Spring MVC to Spring Boot Migration Guide
Overview
This guide provides detailed instructions for migrating Spring MVC applications to Spring Boot, covering all aspects from build configuration to testing.
Pre-Migration Checklist
Before starting migration:
- Backup your code (create git branch)
- Document current application structure
- List all external dependencies
- Note custom configurations
- Identify integration points
- Review current Spring version
- Check Java version compatibility
Migration Process
Phase 1: Build Configuration
Maven Migration
Step 1: Add Spring Boot Parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
Step 2: Replace Dependencies
Remove individual Spring dependencies:
<!-- Remove these -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
Add Spring Boot starters:
<!-- Add these -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Step 3: Add Spring Boot Maven Plugin
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Gradle Migration
Step 1: Add Spring Boot Plugin
plugins {
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
}
Step 2: Replace Dependencies
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Phase 2: Application Structure
Create Main Application Class
Create Application.java in your base package:
package com.example.myapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Update Package Structure
Recommended structure:
src/main/java/com/example/myapp/
├── Application.java
├── controller/
│ └── UserController.java
├── service/
│ └── UserService.java
├── repository/
│ └── UserRepository.java
├── model/
│ └── User.java
└── config/
└── WebConfig.java
Phase 3: Configuration Migration
XML to Java Configuration
Before (XML):
<beans>
<context:component-scan base-package="com.example"/>
<mvc:annotation-driven/>
<bean id="dataSource" class="...">
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
</bean>
</beans>
After (Java + Properties):
@Configuration
public class DatabaseConfig {
// Most configuration is auto-configured
// Only add custom beans if needed
}
# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
web.xml Migration
Spring Boot doesn't use web.xml. Configuration is done through:
- Application properties
- Java configuration classes
- Annotations
Before (web.xml):
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
After: Not needed - Spring Boot auto-configures DispatcherServlet
Phase 4: Controller Migration
Update Annotations
Before:
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public List<User> getUsers() {
return userService.findAll();
}
@RequestMapping(value = "/{id}", method = RequestMethod.POST)
@ResponseBody
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
After:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public List<User> getUsers() {
return userService.findAll();
}
@PostMapping("/{id}")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Key Changes:
@Controller+@ResponseBody→@RestController@RequestMapping(method = RequestMethod.GET)→@GetMapping@RequestMapping(method = RequestMethod.POST)→@PostMapping- Similar for
@PutMapping,@DeleteMapping,@PatchMapping
Phase 5: Service Layer
Service layer typically requires minimal changes:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> findAll() {
return userRepository.findAll();
}
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
}
}
Phase 6: Data Access Layer
JPA/Hibernate Configuration
Before (XML):
<bean id="entityManagerFactory" class="...">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.example.model"/>
</bean>
After (Properties):
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
Repository Layer
Spring Data JPA works the same:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastName(String lastName);
}
Phase 7: Test Migration
Update Test Annotations
Before:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = WebConfig.class)
@WebAppConfiguration
public class UserControllerTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}
}
After:
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testGetUsers() throws Exception {
mockMvc.perform(get("/users"))
.andExpect(status().isOk());
}
}
Key Changes:
@RunWith(SpringJUnit4ClassRunner.class)→ Not needed (JUnit 5)@ContextConfiguration→@SpringBootTest- Manual MockMvc setup →
@AutoConfigureMockMvc @Before→@BeforeEach@Testfromorg.junit.Test→org.junit.jupiter.api.Test
Phase 8: Properties Configuration
Create src/main/resources/application.properties:
# Server Configuration
server.port=8080
server.servlet.context-path=/myapp
# Logging
logging.level.root=INFO
logging.level.com.example=DEBUG
# Database
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# View Resolver (if using JSP)
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
Common Migration Patterns
Pattern 1: Exception Handling
Before:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex) {
ModelAndView mav = new ModelAndView("error");
mav.addObject("message", ex.getMessage());
return mav;
}
}
After (REST API):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
}
Pattern 2: Interceptors
Before:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor());
}
}
After: Same pattern works in Spring Boot
Pattern 3: CORS Configuration
Before:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000");
}
}
After: Same pattern or use properties:
spring.web.cors.allowed-origins=http://localhost:3000
spring.web.cors.allowed-methods=GET,POST,PUT,DELETE
Post-Migration Steps
Build the application
mvn clean packageRun the application
java -jar target/myapp.jar # or mvn spring-boot:runTest endpoints
curl http://localhost:8080/usersRun tests
mvn testCheck actuator endpoints (if enabled)
curl http://localhost:8080/actuator/health
Troubleshooting
Issue: Application won't start
Solution: Check for:
- Missing
@SpringBootApplicationannotation - Incorrect package structure
- Conflicting dependencies
Issue: Controllers not found
Solution: Ensure:
- Controllers are in same package or sub-package as Application class
@ComponentScanis not restricting scan
Issue: Database connection fails
Solution: Verify:
- Database URL in application.properties
- Database driver dependency
- Database is running
Issue: Tests fail
Solution: Update:
- JUnit 4 to JUnit 5
- Test annotations
- MockMvc setup
Best Practices
- Migrate incrementally - One module at a time
- Keep tests passing - Run tests after each change
- Use Spring Boot starters - Don't mix with individual dependencies
- Leverage auto-configuration - Minimize custom configuration
- Use application.properties - Externalize configuration
- Enable Actuator - For production monitoring
- Follow conventions - Use standard package structure
Next Steps
After successful migration:
- Enable Spring Boot Actuator for monitoring
- Add Spring Boot DevTools for development
- Configure profiles for different environments
- Set up logging configuration
- Add API documentation (Swagger/OpenAPI)
- Configure security (Spring Security)
- Optimize for production deployment