Java 25 Migration Guide (21→25)

1. Introduction

Java 25 is the next Long-Term Support (LTS) release after Java 21, expected in September 2025. If your organization runs on Java 21 — which it should if you followed the last migration guide — Java 25 is the natural next upgrade target. Between Java 21 and Java 25, four feature releases shipped (22, 23, 24, 25), each adding language features, API improvements, and runtime enhancements.

The upgrade from 21 to 25 is less disruptive than the jump from 17 to 21. There are no paradigm-shifting features like virtual threads this time around. Instead, Java 25 polishes and finalizes features that were in preview during Java 21, adds new language conveniences, and delivers meaningful performance improvements. Think of it as Java 21 with the rough edges smoothed out.

LTS Support Timelines

Version Release Date Type Oracle Premier Support Until Extended Support Until
Java 17 September 2021 LTS September 2026 September 2029
Java 21 September 2023 LTS September 2028 September 2031
Java 22 March 2024 Non-LTS September 2024 N/A
Java 23 September 2024 Non-LTS March 2025 N/A
Java 24 March 2025 Non-LTS September 2025 N/A
Java 25 September 2025 LTS September 2030 September 2033

Why migrate now? Java 21 premier support runs until September 2028, so there is no rush. But the new features in Java 25 — particularly finalized structured concurrency, scoped values, and the performance improvements — provide real value. Planning your migration now gives you time to test thoroughly and adopt new features incrementally.

2. New Language Features Summary (Java 22-25)

Here is a comprehensive table of every significant feature added between Java 22 and Java 25. Features marked Final are production-ready. Features marked Preview require --enable-preview to use.

Feature JEP Introduced Finalized Status in Java 25
Module Import Declarations 476 Java 23 (preview) Java 25 Final
Implicitly Declared Classes & Instance Main 477 Java 21 (preview as JEP 445) Java 25 Final
Primitive Types in Patterns 488 Java 23 (preview) Java 25 Final
Flexible Constructor Bodies 492 Java 22 (preview) Java 25 Final
Structured Concurrency 499 Java 19 (incubator) Java 25 (expected) Final (expected)
Scoped Values 487 Java 20 (incubator) Java 25 (expected) Final (expected)
Class-File API 484 Java 22 (preview) Java 24 Final
Foreign Function & Memory API 454 Java 14 (incubator) Java 22 Final
Unnamed Patterns and Variables 456 Java 21 (preview) Java 22 Final
Statements Before super() 492 Java 22 (preview) Java 25 Final
Stream Gatherers 485 Java 22 (preview) Java 24 Final
Key Derivation Function API 478 Java 24 Java 24 Final
AOT Class Loading & Linking 483 Java 24 Java 24 Final
Stable Values 502 Java 25 Preview
Compact Object Headers 450 Java 24 Experimental
Vector API 489 Java 16 (incubator) Incubator

The theme of Java 22-25 is finalization. Many features that were in preview or incubator during Java 21 have graduated to production-ready status. This means you can use them without --enable-preview flags and rely on them in production code with confidence.

3. Breaking Changes

The migration from Java 21 to Java 25 has fewer breaking changes than the 17-to-21 jump, but there are important ones to be aware of:

3.1 Removed Features

Removed Feature Removed In Replacement Action Required
String Templates (STR, FMT processors) Java 23 None yet (may return in different form) Remove any preview usage of STR."..."
sun.misc.Unsafe memory methods (partial) Java 23+ Foreign Function & Memory API (java.lang.foreign) Migrate to MemorySegment and Arena
Windows 32-bit x86 port Java 24 Use 64-bit JDK Switch to 64-bit if running 32-bit Windows

3.2 Behavioral Changes

Change Version Impact What to Do
Integrity by default — stronger module encapsulation Java 24 Illegal reflective access warnings become errors Add --add-opens or fix code to use public APIs
UTF-8 by default (already in Java 18) Java 18+ File I/O uses UTF-8 regardless of system locale Verify encoding assumptions in file operations
Deprecation enforcement Various Previously deprecated methods may be removed in future versions Address deprecation warnings now
Security Manager restrictions Java 24 Cannot install a SecurityManager at all Remove SecurityManager usage entirely

3.3 The Integrity by Default Change (Important)

This is the most impactful behavioral change. Starting in Java 24, the JVM enforces module boundaries more strictly. Libraries that use deep reflection to access internal JDK classes will fail at runtime instead of just warning. This primarily affects:

  • Serialization libraries that access private fields via reflection (Jackson, Gson older versions)
  • ORM frameworks that create proxy classes (Hibernate, older versions)
  • Testing frameworks that mock final classes or private methods (older Mockito versions)
  • Application servers that manipulate classloading

The fix is to upgrade to recent versions of these libraries (which already handle the restrictions properly) or add --add-opens flags as a temporary workaround.

4. Step-by-Step Migration Checklist

Follow these ten steps to migrate from Java 21 to Java 25. Each step should be a separate commit or PR so you can isolate issues.

Step 1: Audit Your Current Setup

// Check your current Java version
// $ java -version
// openjdk version "21.0.x" ...

// Check for deprecation warnings in your build
// $ mvn compile 2>&1 | grep -i "deprecat"
// $ gradle compileJava 2>&1 | grep -i "deprecat"

// List all --add-opens and --add-exports flags you currently use
// $ grep -r "add-opens\|add-exports" pom.xml build.gradle Dockerfile

Step 2: Update Dependencies First

Before changing the JDK version, update your dependencies to versions that support Java 25. This is the step that catches most issues.

// Key dependencies to update (minimum versions for Java 25 support):
//
// Build plugins:
//   maven-compiler-plugin   >= 3.13
//   maven-surefire-plugin   >= 3.3
//   gradle                  >= 8.8
//
// Frameworks:
//   Spring Boot             >= 3.4 (recommended: 3.5+)
//   Spring Framework        >= 6.2 (recommended: 6.3+)
//   Quarkus                 >= 3.15+
//   Micronaut               >= 4.7+
//
// Libraries:
//   Jackson                 >= 2.17
//   Hibernate               >= 6.5
//   Lombok                  >= 1.18.34
//   Mockito                 >= 5.12
//   JUnit                   >= 5.11
//   Byte Buddy              >= 1.15
//   ASM                     >= 9.7

Step 3: Install JDK 25

// Option 1: SDKMAN (recommended for developers)
// $ sdk install java 25-open
// $ sdk use java 25-open

// Option 2: Eclipse Temurin (recommended for production)
// Download from https://adoptium.net/temurin/releases/

// Option 3: Amazon Corretto
// Download from https://aws.amazon.com/corretto/

// Verify installation
// $ java -version
// openjdk version "25" 2025-09-16

Step 4: Update Build Configuration

// Maven pom.xml
// 
//     25
//     25
//     25
//     25
// 

// Gradle build.gradle
// java {
//     toolchain {
//         languageVersion = JavaLanguageVersion.of(25)
//     }
// }

// Gradle build.gradle.kts (Kotlin DSL)
// java {
//     toolchain {
//         languageVersion.set(JavaLanguageVersion.of(25))
//     }
// }

Step 5: Compile and Fix Errors

Run a clean build and address compilation errors. Common issues:

Error Cause Fix
cannot access class sun.misc.Unsafe Direct Unsafe usage Migrate to java.lang.foreign API or VarHandle
module X does not export Y Accessing internal APIs Use public API alternatives or --add-exports
class file has wrong version 69.0 Dependency compiled with newer Java than build tool expects Update build tool plugins
Preview feature warnings Using features that were preview in 21 but changed since Update code to use finalized syntax

Step 6: Run Tests

// Run the full test suite
// $ mvn test -Dsurefire.useFile=false
// $ gradle test --info

// Pay attention to:
// 1. Reflection-based tests (may fail due to stronger encapsulation)
// 2. Serialization tests (format may differ between JDK versions)
// 3. Tests that depend on internal JDK behavior (GC, classloading order)
// 4. Tests that parse java -version output or check system properties

Step 7: Check Runtime Behavior

Some issues only appear at runtime. Start your application and verify:

  • Application starts without warnings or errors
  • All reflection-heavy features work (dependency injection, ORM, serialization)
  • Performance is equal to or better than Java 21
  • Memory usage is stable
  • Third-party integrations (databases, message queues, caches) connect successfully

Step 8: Address Deprecation Warnings

// Compile with deprecation warnings visible
// $ mvn compile -Xlint:deprecation
// $ gradle compileJava -Xlint:deprecation

// Common deprecations to address:
// - SecurityManager usage -> remove entirely
// - Finalize methods -> use Cleaner or try-with-resources
// - Thread.stop(), Thread.suspend() -> use interrupt-based signaling
// - Old Date/Calendar APIs -> use java.time

Step 9: Optimize with New Features (Optional)

Once the migration is stable, selectively adopt new features:

Feature Migration Effort Benefit Priority
Module imports Low — IDE refactor Cleaner import blocks Low (cosmetic)
Structured concurrency Medium — refactor concurrent code Safer, more maintainable concurrency High if using concurrency
Scoped values Medium — replace ThreadLocal No memory leaks, better with virtual threads High if using ThreadLocal
Primitive patterns Low — refactor switch/if-else Cleaner pattern matching Medium
AOT class loading Low — deployment config only Faster startup High for microservices
Stream Gatherers Low — new code or refactor Custom stream operations Low to Medium

Step 10: Update Deployment Infrastructure

Update your deployment pipeline, Docker images, and CI/CD configuration (covered in detail in sections 8 and 9 below).

5. Build Tool Updates

5.1 Maven Configuration

// pom.xml -- complete Maven configuration for Java 25
// 
//     
//         25
//         25
//     
//
//     
//         
//             
//                 org.apache.maven.plugins
//                 maven-compiler-plugin
//                 3.13.0
//                 
//                     25
//                     
//                     
//                 
//             
//
//             
//                 org.apache.maven.plugins
//                 maven-surefire-plugin
//                 3.3.1
//                 
//                     
//                     
//                 
//             
//         
//     
// 

5.2 Gradle Configuration

// build.gradle.kts -- Kotlin DSL
// plugins {
//     java
//     id("org.springframework.boot") version "3.5.0"
// }
//
// java {
//     toolchain {
//         languageVersion.set(JavaLanguageVersion.of(25))
//     }
// }
//
// tasks.withType {
//     options.release.set(25)
//     // For preview features:
//     // options.compilerArgs.add("--enable-preview")
// }
//
// tasks.withType {
//     useJUnitPlatform()
//     // For preview features:
//     // jvmArgs("--enable-preview")
// }

// build.gradle -- Groovy DSL
// java {
//     toolchain {
//         languageVersion = JavaLanguageVersion.of(25)
//     }
// }
//
// compileJava {
//     options.release = 25
// }

6. Framework Compatibility

Here is the compatibility matrix for major Java frameworks with Java 25:

Framework Minimum Version for Java 25 Recommended Version Notes
Spring Boot 3.4.x 3.5.x or 4.0.x 3.5+ expected to have official Java 25 support
Spring Framework 6.2.x 6.3.x or 7.0.x Spring Framework 7 targets Java 25 as baseline
Quarkus 3.15+ 3.17+ or 4.x Quarkus adds Java support quickly after release
Micronaut 4.7+ 4.8+ Good Java version support track record
Jakarta EE 10 11 Jakarta EE 11 aligns with Java 25 features
Hibernate 6.5+ 6.6+ Ensure Byte Buddy version is compatible
Lombok 1.18.34+ Latest Lombok is sensitive to JDK internals — always use latest
MapStruct 1.6+ Latest Annotation processors need compiler compatibility

6.1 Spring Boot Migration Tips

// application.properties updates for Java 25

// Enable virtual threads (already available since Spring Boot 3.2 + Java 21)
spring.threads.virtual.enabled=true

// If using AOT class loading, configure the training profile
// spring.profiles.active=aot-training

// Structured concurrency with Spring -- example service
import java.util.concurrent.StructuredTaskScope;

@Service
public class UserProfileService {

    @Autowired private UserRepository userRepo;
    @Autowired private OrderRepository orderRepo;

    public UserProfile getProfile(long userId) throws Exception {
        try (var scope = StructuredTaskScope.open()) {
            var userTask = scope.fork(() -> userRepo.findById(userId));
            var ordersTask = scope.fork(() -> orderRepo.findByUserId(userId));
            scope.join();
            return new UserProfile(userTask.get(), ordersTask.get());
        }
    }
}

7. Common Migration Issues

Based on community experience with Java 22-24 migrations, here are the most common issues and their solutions:

# Issue Symptom Solution
1 Illegal reflective access InaccessibleObjectException at runtime Update the library to a version that uses public APIs, or add --add-opens java.base/java.lang=ALL-UNNAMED as a temporary workaround
2 Lombok compilation failure java.lang.IllegalAccessError during annotation processing Update Lombok to >= 1.18.34. Lombok accesses JDK internals and needs frequent updates
3 Byte Buddy / Mockito failure IllegalArgumentException: Unsupported class file version Update Byte Buddy to >= 1.15 and Mockito to >= 5.12
4 Jackson serialization issues Reflection errors on record types or sealed classes Update Jackson to >= 2.17; ensure jackson-module-parameter-names is included
5 SecurityManager removal UnsupportedOperationException: The Security Manager is deprecated Remove all SecurityManager code; it cannot be installed in Java 24+
6 ASM version incompatibility Build tools or plugins fail to parse class files Update ASM to >= 9.7 (via dependency management or plugin updates)
7 Finalizer deprecation warnings Warnings about overriding finalize() Replace with Cleaner or explicit close() methods with try-with-resources
8 Preview feature code from Java 21 Compilation errors for preview syntax that changed Remove String Templates usage (removed in 23); update any preview feature syntax
9 Test framework incompatibility Tests fail to start or mock creation fails Update JUnit to 5.11+ and test dependency versions
10 Docker base image not available No eclipse-temurin:25 image found Wait for Adoptium release (usually within days of GA) or use early-access builds

8. Performance Improvements

Java 25 delivers measurable performance improvements in several areas. Here is what you can expect without changing any application code:

8.1 AOT Class Loading & Linking

The biggest startup improvement. By pre-loading and pre-linking classes, the JVM skips the expensive discovery, verification, and linking phases that dominate startup time. This benefits all applications but is most impactful for large applications with many classes (Spring Boot applications, Jakarta EE servers).

Metric Java 21 Java 25 (without AOT) Java 25 (with AOT cache)
Spring Boot startup ~4.0s ~3.5s ~1.5s
Classes loaded at startup ~12,000 ~12,000 ~12,000 (from cache)
Time to first request ~5.0s ~4.5s ~2.0s

8.2 Compact Object Headers

Reduces every object’s header from 12 bytes to 8 bytes. For applications with millions of small objects (collections-heavy code, graph structures, caches), this translates to 10-20% heap memory savings. Enable with -XX:+UseCompactObjectHeaders (experimental in Java 25).

8.3 Generational ZGC Improvements

ZGC’s generational mode (default since Java 23) has been further tuned:

  • Lower pause times: Sub-millisecond pauses are more consistently achieved
  • Better throughput: Young generation collection is more efficient
  • Less tuning needed: Default heap region sizes and collection triggers are smarter
  • Lower memory overhead: ZGC’s own memory usage has been reduced

8.4 JIT Compiler Improvements

The C2 JIT compiler includes better escape analysis, improved loop optimizations, and enhanced auto-vectorization. These improvements benefit all code without any configuration changes. Typical throughput improvement is 2-5% compared to Java 21 for compute-heavy workloads.

9. Docker and CI/CD Updates

9.1 Updated Dockerfile

// Multi-stage Dockerfile for Java 25 with AOT cache
//
// # Build stage
// FROM eclipse-temurin:25-jdk AS builder
// WORKDIR /app
// COPY pom.xml .
// COPY src ./src
// RUN mvn package -DskipTests
//
// # AOT training stage
// FROM eclipse-temurin:25-jre AS aot-trainer
// WORKDIR /app
// COPY --from=builder /app/target/myapp.jar .
// RUN java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \
//     -Dspring.context.exit=onRefresh -jar myapp.jar || true
// RUN java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \
//     -XX:AOTCache=app.aot -jar myapp.jar
//
// # Production stage
// FROM eclipse-temurin:25-jre
// WORKDIR /app
// COPY --from=builder /app/target/myapp.jar .
// COPY --from=aot-trainer /app/app.aot .
//
// ENV JAVA_OPTS="-XX:AOTCache=/app/app.aot \
//     -XX:+UseZGC -XX:+ZGenerational \
//     -XX:MaxRAMPercentage=75"
//
// EXPOSE 8080
// ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar myapp.jar"]

9.2 Base Image Options

Image Size Use Case
eclipse-temurin:25-jre ~200MB Standard production image
eclipse-temurin:25-jre-alpine ~100MB Smaller image, Alpine-based
eclipse-temurin:25-jdk ~350MB Build stage or development
amazoncorretto:25 ~220MB AWS-optimized, good for ECS/EKS
azul/zulu-openjdk:25 ~210MB Azul-supported, good for Azure

9.3 CI/CD Pipeline Updates

// GitHub Actions workflow for Java 25
//
// name: Build and Test
// on: [push, pull_request]
//
// jobs:
//   build:
//     runs-on: ubuntu-latest
//     steps:
//       - uses: actions/checkout@v4
//
//       - name: Set up JDK 25
//         uses: actions/setup-java@v4
//         with:
//           java-version: '25'
//           distribution: 'temurin'
//           cache: 'maven'
//
//       - name: Build and test
//         run: mvn verify
//
//       - name: Upload artifact
//         uses: actions/upload-artifact@v4
//         with:
//           name: app-jar
//           path: target/*.jar

10. Best Practices for Adoption

10.1 Adopt Features Incrementally

Do not try to adopt every new feature at once. Here is a recommended order:

  1. Week 1-2: Get the application compiling and tests passing on Java 25 with zero code changes
  2. Week 3-4: Enable AOT class loading for faster startup (deployment config only, no code changes)
  3. Month 2: Start using module imports in new code
  4. Month 2-3: Replace ThreadLocal with ScopedValue where appropriate
  5. Month 3-4: Refactor concurrent code to use structured concurrency
  6. Ongoing: Use primitive patterns, flexible constructors, and other language features in new code

10.2 LTS Stability Considerations

Java 25 as an LTS release will receive updates for years. Here are best practices for stability:

  • Pin your JDK version: Use specific update releases (e.g., 25.0.2) rather than just 25 in production. Update deliberately, not automatically.
  • Avoid preview features in production: Preview features can change between releases. Use them for experimentation and testing, but not in production code that needs to survive JDK updates.
  • Test with the exact JDK you will deploy: Temurin, Corretto, and Oracle JDK can have subtle behavioral differences. Test with the same distribution you run in production.
  • Monitor JDK release notes: Each quarterly update (25.0.1, 25.0.2, etc.) may include behavioral changes. Read the release notes before upgrading.

10.3 Migration Timeline Recommendation

Phase Timeline Goal
Evaluation October 2025 Build and test on Java 25, identify issues
Development November-December 2025 Fix issues, update dependencies, run full test suite
Staging January 2026 Deploy to staging/pre-production, performance testing
Production rollout February-March 2026 Gradual production deployment (canary, then full)
Feature adoption Q2 2026 onward Incrementally adopt new language features and APIs

11. Summary

Migrating from Java 21 to Java 25 is a straightforward upgrade with high reward. The breaking changes are minimal (primarily around module encapsulation enforcement and SecurityManager removal), and the benefits are substantial:

  • Finalized concurrency APIs: Structured concurrency and scoped values are production-ready, giving you safer, more maintainable concurrent code
  • Language improvements: Module imports, primitive patterns, flexible constructors, and simplified source files make Java code more concise and expressive
  • Performance gains: AOT class loading for 2-3x faster startup, compact object headers for 10-20% less memory, improved ZGC and JIT compilation
  • Better developer experience: Simpler programs for teaching and scripting, less boilerplate everywhere

The migration playbook is: update dependencies first, change the JDK version, fix compilation and test failures, then adopt new features incrementally. Most teams should be able to complete the core migration in two to four weeks, with feature adoption continuing over the following months.

Java 25 is a worthy successor to Java 21. It does not introduce another paradigm shift like virtual threads, but it polishes, finalizes, and optimizes everything that Java 21 started. If you are on Java 21, start planning your Java 25 migration now. If you are still on Java 17 or earlier, consider jumping directly to Java 25 — you will get the best of both worlds.




Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *