Upgrading Java Applications from JDK 1.8 to JDK 21 with Spring Boot 3

This guide documents key technical adjustments required when modernizing a legacy Java application—from JDK 1.8 and Spring Boot 2.x to JDK 21 and Spring Boot 3.2+. The migration involves breaking API changes, dependency updates, and configuration refactoring across multiple layers: servlet stack, security, persistence, serialization, and tooling.

Prerequisites

  • JDK 21 installde (LTS)
  • Maven 3.9.0 or newer
  • IDE configured for Java 21 language level (e.g., IntelliJ: File → Project Structure → Project SDK & Language Level → 21)

Core Build Configuration Updates

In pom.xml, align compiler and platform versions:

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <maven.compiler.source>21</maven.compiler.source>
  <maven.compiler.target>21</maven.compiler.target>
  <java.version>21</java.version>
  <spring-boot.version>3.2.4</spring-boot.version>
</properties>

Servlet Namespace Migration

Spring Boot 3 adopts the Jakarta EE 9+ namespace. Replace all javax.servlet.* imports with jakarta.servlet.*:

  • javax.servlet.http.HttpServletRequestjakarta.servlet.http.HttpServletRequest
  • javax.servlet.Filterjakarta.servlet.Filter

Remove any javax.* dependencies from pom.xml; they’re incompatible and will cause NoClassDefFoundError.

Spring Security 6 Refactor

The WebSecurityConfigurerAdapter class is removed. Replace it with functional bean registration:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .csrf(csrf -> csrf.disable())
      .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
      .httpBasic(Customizer.<HttpBasicConfigurer>nullSafe())
      .formLogin(Customizer.<FormLoginConfigurer<HttpSecurity>>nullSafe())
      .logout(Customizer.<LogoutConfigurer<HttpSecurity>>nullSafe())
      .exceptionHandling(ex -> ex
        .authenticationEntryPoint(new RestAuthenticationEntryPoint())
        .accessDeniedHandler(new RestfulAccessDeniedHandler()))
      .authorizeHttpRequests(authz -> authz
        .requestMatchers(HttpMethod.GET, "/actuator/**", "/swagger-ui/**").permitAll()
        .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
        .anyRequest().authenticated());
    return http.build();
  }
}

Note: authorizeHttpRequests() replaces deprecated antMatchers(). Rule ordering matters—anyRequest().authenticated() must be last.

MyBatis Integration Fixes

Upgrade MyBatis integrations to Spring Boot 3–compatible versions:

  • Use mybatis-spring 3.0.3 (not older versions)
  • Exclude transitive mybatis-spring from mybatis-plus-boot-starter if used
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>3.0.3</version>
</dependency>

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.5</version>
  <exclusions>
    <exclusion>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </exclusion>
  </exclusions>
</dependency>

Ensure SqlSessionFactoryBean or SqlSessionTemplate is explicitly declared if using custom configuration.

JWT Token Handling

Legacy javax.xml.bind.DatatypeConverter is removed in JDK 21. Migrate to modern JWT libraries:

  • Replace jjwt-api:0.11.x with io.jsonwebtoken:jjwt-api:0.12.5
  • Update signing/verification logic to use Jwts.builder() and Jwts.parserBuilder()

API Documentation Repllacement

Springfox is incompatible with Jakarta EE. Switch to springdoc-openapi or knife4j:

<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  <version>2.3.0</version>
</dependency>

Update permitted endpoints in security rules: e.g., permit /v3/api-docs/** and /swagger-ui/**.

Redis Serialization Update

Replace deprecated setObjectMapper() with RedisSerializationContext-based configuration:

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  RedisTemplate<String, Object> template = new RedisTemplate<>();
  template.setConnectionFactory(factory);
  template.setDefaultSerializer(RedisSerializer.json());
  template.setKeySerializer(RedisSerializer.string());
  template.setValueSerializer(RedisSerializer.json());
  template.setHashKeySerializer(RedisSerializer.string());
  template.setHashValueSerializer(RedisSerializer.json());
  return template;
}

Build Tooling Notes

  • If Maven fails with invalid flag: --release in IDE, run mvn clean compile directly in terminal — this often bypasses misconfigured IDE Maven runner flags.
  • Verify that no third-party plugins rely on internal JDK APIs (e.g., com.sun.tools.javac). Upgrade or replace such plugins.

Data Access Layer Adjustments

For Spring Data modules:

  • Elasticsearch: Downgrade spring-data-elasticsearch to 4.4.x if ElasticsearchRepository.deleteAll() is missing.
  • MongoDB: Pin spring-data-mongodb to 4.1.5 (or latest 4.x compatible with Spring Boot 3.2) to resolve ListCrudRepository resolution failures.
  • MySQL: Append ?allowPublicKeyRetrieval=true&useSSL=false to JDBC URL if encountering Public Key Retrieval is not allowed.

Tags: JDK21 spring-boot-3 spring-security-6 mybatis-spring jakarta-ee

Posted on Mon, 17 Aug 2026 16:51:34 +0000 by jamcoupe