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.
| 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.
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.
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:
| 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 |
| 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 |
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:
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.
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.
// 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
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
// 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
// Maven pom.xml //// // Gradle build.gradle // java { // toolchain { // languageVersion = JavaLanguageVersion.of(25) // } // } // Gradle build.gradle.kts (Kotlin DSL) // java { // toolchain { // languageVersion.set(JavaLanguageVersion.of(25)) // } // }25 //25 //25 //25 //
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 |
// 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
Some issues only appear at runtime. Start your application and verify:
// 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
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 |
Update your deployment pipeline, Docker images, and CI/CD configuration (covered in detail in sections 8 and 9 below).
// 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 //// // // //
// 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
// }
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 |
// 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());
}
}
}
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 |
Java 25 delivers measurable performance improvements in several areas. Here is what you can expect without changing any application code:
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 |
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).
ZGC’s generational mode (default since Java 23) has been further tuned:
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.
// 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"]
| 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 |
// 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
Do not try to adopt every new feature at once. Here is a recommended order:
ThreadLocal with ScopedValue where appropriateJava 25 as an LTS release will receive updates for years. Here are best practices for stability:
25.0.2) rather than just 25 in production. Update deliberately, not automatically.| 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 |
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:
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.