Motivation for the Upgrade
Legacy systems often rely on traditional Java Server Pages (JSP) and WAR deployments. Rather than rewriting the frontend, this guide demonstrates how to modernize the backend by migrating to Spring Boot 3.0, Spring Framework 6.0, and JDK 17, while maintaining JSP support and WAR packaging.
Key reasons for upgrading include:
- JDK 17 offers significant performance improvements and is the current Long-Term Support (LTS) standard.
- Spring 6 simplifies configuration compared to older XML-heavy Spring 5.x setups.
- Tooling ecosystems (IDEs, build tools) are increasingly optimizing for JDK 17.
Reasons for retaining WAR and JSP:
- WAR allows partial updates (replacing specific classes or views) without redeploying the entire application.
- Externalizing Tomcat configuration is easier with a WAR deployment.
- JSP remains a cost-effective option for existing view layers, supported by both modern Tomcat and Spring.
Environment Setup
- OS: Windows 11
- IDE: Spring Tool Suite (STS) 4.14.1
- Server: Tomcat 10.0.22
- JDK: 17
- Frontend: jQuery 3.6.0
1. Project Initialization
Generate a project using Spring Initializr or the STS wizard. Ensure the following selections:
- Packaging: WAR
- Java Version: 17
2. Bootstrap Configuration
Create the main application class. It must extend SpringBootServletInitializer to support traditional WAR deployment on Servlet containers like Tomcat 10.
package com.example.legacyapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class LegacyAppStarter extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(LegacyAppStarter.class);
}
public static void main(String[] args) {
SpringApplication.run(LegacyAppStarter.class, args);
}
}
3. Maven Dependencies (pom.xml)
The most critical step is configuring the pom.xml to include JSP engine dependencies and the WAR plugin. Note that Spring Boot 3 uses Jakarta EE (jakarta.* packages) instead of Java EE (javax.*).
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>legacy-web-app</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Web Starter (Exclude embedded Tomcat to avoid conflicts with external Tomcat 10) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Provided Tomcat APIs for compilation -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>10.0.22</version>
<scope>provided</scope>
</dependency>
<!-- JSP Support -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>compile</scope>
</dependency>
<!-- JSTL Tag Library -->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- WAR Plugin Configuration -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
4. Web Configuration (Java Config)
Instead of web.xml, use a configuration class extending WebMvcConfigurationSupport. This handles view resolvers, static resources, and filter registration.
package com.example.legacyapp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import jakarta.servlet.Filter;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
public class WebAppConfig extends WebMvcConfigurationSupport {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
}
@Bean
public Filter customRequestFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
jakarta.servlet.FilterChain chain)
throws java.io.IOException, jakarta.servlet.ServletException {
System.out.println("Request URI: " + request.getRequestURI());
chain.doFilter(request, response);
}
};
}
}
5. Controller and View
Create a simple controller that returns data for the JSP view.
package com.example.legacyapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DashboardController {
@GetMapping("/dashboard")
public String showDashboard(Model model) {
model.addAttribute("message", "Successfully running on Spring Boot 3 + JDK 17");
return "dashboard"; // Resolves to /WEB-INF/jsp/dashboard.jsp or similar depending on resolver
}
}
Example JSP (src/main/webapp/dashboard.jsp):
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>Legacy App Modernized</title>
<script src="/assets/jquery-3.6.0.min.js"></script>
</head>
<body>
<h1 style="color: green;">${message}</h1>
<div id="content">Loaded via JSP</div>
</body>
</html>
6. Building the WAR
Run the Maven command to package the application:
mvn clean package -DskipTests
This generates a .war file in the target directory.
7. Deployment to Tomcat 10
- Ensure
JAVA_HOMEpoints to JDK 17 in Tomcat'sbin/setenv.bat(orcatalina.sh). - Copy the generated
legacy-web-app.warinto thewebappsfolder of Tomcat 10. - Start Tomcat using
startup.bat.
Access the application at http://localhost:8080/legacy-web-app/.