Backend Development 13 min read

Upgrade Java Projects to JDK 21: Challenges, Solutions & Best Practices

This article outlines the motivations, challenges, and step‑by‑step solutions for migrating over 100 Java applications to JDK 21, covering dependency conflicts, module system adjustments, Maven plugin compatibility, garbage‑collector selection, and practical build and deployment practices to ensure a smooth upgrade.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Upgrade Java Projects to JDK 21: Challenges, Solutions & Best Practices

Background and Challenges

Upgrade Drivers

Oracle long‑term support strategy

Modern feature requirements: coroutines, pattern matching, ZGC, etc.

Security and performance needs

Version requirements introduced by new AI technologies

Project Situation

Coordinated upgrade of over 100 parallel projects

Multiple technology stacks coexist

Continuous‑integration system adaptation challenges

Progress

Application Total

Completed

Offline

Pending Upgrade

100+

73

13

10+

Main Problem Domains and Solutions

1. Dependency “Butterfly Effect”

sun.misc.BASE64Encoder and other internal APIs deprecated → compilation errors

JAXB/JAX‑WS removed from JDK core → XML processing chain broken

Lombok compatibility issues with new compiler (especially record types)

Root cause is JEP 320: https://openjdk.org/jeps/320

Case 1: Legacy SDK compilation trap

<code>Compilation failure: Compilation failure:
#14 4.173 [ERROR] Source option 6 is no longer supported. Use 8 or higher.
#14 4.173 [ERROR] Target option 6 is no longer supported. Use 8 or higher.</code>
<code><!-- Old compiler configuration causing build failure -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.5</version>
  <configuration>
    <source>1.6</source>
    <target>1.6</target>
  </configuration>
</plugin></code>

Updated configuration:

<code><plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.13.0</version>
  <configuration>
    <release>8</release><!-- Use release parameter -->
  </configuration>
</plugin></code>

Case 2: JAXB modular removal

<code>javax.xml.bind.JAXBException: Implementation of JAXB‑API has not been found</code>
<code><dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>4.0.5</version>
</dependency></code>

Case 3: Lombok and new compiler compatibility

<code>java: java.lang.NoSuchFieldError</code>
<code><dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.30</version>
</dependency></code>

Case 4: Missing Resource annotation

<code>Caused by: java.lang.NoSuchMethodError: 'java.lang.String javax.annotation.Resource.lookup()'</code>
<code><dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
  <version>1.3.5</version>
</dependency>
<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>javax.annotation-api</artifactId>
  <version>1.3.2</version>
</dependency></code>

Recommended unified version: jakarta.annotation:jakarta.annotation-api (2.1.1)

2. Modular Break and Build

Reflection access module wall

<code>[ERROR] Unable to make field private int java.text.SimpleDateFormat.serialVersionOnStream accessible</code>
<code># Add module opens
--add-opens java.base/java.text=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED</code>

Full module‑open configuration template:

<code>export JAVA_OPTS="-Djava.library.path=/usr/local/lib -server -Xmx4096m \
--add-opens java.base/sun.security.action=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.math=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/sun.util.calendar=ALL-UNNAMED \
--add-opens java.base/java.util.concurrent=ALL-UNNAMED \
--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED \
--add-opens java.base/java.security=ALL-UNNAMED \
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED \
--add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED \
--add-opens java.base/java.net=ALL-UNNAMED \
--add-opens java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens java.management/java.lang.management=ALL-UNNAMED \
--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED \
--add-opens java.management/sun.management=ALL-UNNAMED \
--add-opens java.base/sun.security.action=ALL-UNNAMED \
--add-opens java.base/sun.net.util=ALL-UNNAMED \
--add-opens java.base/java.time=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens java.base/java.io=ALL-UNNAMED"</code>

3. Syntax “Time‑Travel”

Case 1: Base64 encoding refactor

<code>// JDK8 deprecated way
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(data);
// JDK21 standard way
Base64.Encoder encoder = Base64.getEncoder();
String encoded = encoder.encodeToString(data);</code>

Case 2: Date serialization issue

<code>Caused by: java.lang.reflect.InaccessibleObjectException:
Unable to make field private int java.text.SimpleDateFormat.serialVersionOnStream accessible</code>

Solution

Replace SimpleDateFormat with DateTimeFormatter

Add module‑open parameter: --add-opens java.base/java.text=ALL-UNNAMED

4. Hidden “Dependency War”

Annotation package conflict example

<code>[ERROR] javax.annotation.Resource exists in both jsr250-api-1.0.jar and jakarta.annotation-api-1.3.5.jar</code>
<code><!-- Use Jakarta standard -->
<dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
  <version>2.1.1</version>
</dependency>
<!-- Exclude old version -->
<exclusions>
  <exclusion>
    <groupId>javax.annotation</groupId>
    <artifactId>jsr250-api</artifactId>
  </exclusion>
</exclusions></code>

5. Build System Refactor

Maven plugin compatibility issue

<code>[ERROR] The plugin org.apache.maven.plugins:maven-compiler-plugin:3.13.0 requires Maven version 3.6.3</code>

Upgrade strategy:

Upgrade Maven version

Standardize plugin versions

<code><build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.4.0</version>
      </plugin>
    </plugins>
  </pluginManagement>
</build></code>

Best‑Practice Summary

1. Local Compilation

Compile locally first to detect syntax errors, version conflicts, and incompatibilities. Key scenarios include Base64 refactor, Lombok version upgrade, annotation package conflicts, and Maven plugin upgrades.

2. Cloud Build

Same as local compilation, but executed in CI/CD environment.

3. Cloud Deployment

a) Use matching JDK21 Docker image or custom image. b) Apply full module‑open configuration template. c) Handle JDSecurity encryption/decryption. d) Process important.properties with PropertyPlaceholderConfigurer, not JDSecurityPropertyFactoryBean.

4. Runtime Issues

a) Serialization exception caused by list view input; fix by converting sublist to new ArrayList. b) Thread‑context class not found; prefer explicit thread pools in multithreaded scenarios.

5. JVM Tuning

Garbage‑collector options:

Feature

UseParallelGC

UseG1GC

UseZGC

Design Goal

High throughput

Balance throughput and latency

Ultra‑low latency

Pause Time

Longer

Shorter

Very short

Heap Size

Small‑to‑medium (GBs)

Large (tens‑to‑hundreds GB)

Very large (TB)

CPU Cost

Medium

Medium

Higher

Typical Use

Batch, compute‑intensive

Latency‑sensitive apps

Latency‑critical apps

Choose the GC that matches your application's throughput and latency requirements.

Image
Image
javaBackend DevelopmentGarbage Collectiondependency managementmavenJDK21Module System
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.