1.3 Lts Vs Non Lts Migration
Release Cadence Overview
Since Java 9, Oracle releases a new feature version every six months:
- LTS (Long-Term Support) releases occur every 3 years (Java 8, 11, 17, 21, 25, etc.)
- Non-LTS releases between LTS versions receive 6 months of support
- Java 25 is a non-LTS release supported until September 2025
LTS releases receive updates for 5+ years, making them suitable for long-lived production systems.
When to Upgrade to Java 25
Good Reasons to Upgrade
- You're on Java 21 (LTS) and want latest features within the same support window
- You need Virtual Threads for high-concurrency workloads and cannot wait for Java 27 (next LTS)
- You're on legacy Java (8, 11, 17) and want modern API patterns and performance improvements
- Your dependencies require Java 25 (increasingly common as the ecosystem evolves)
Reasons to Wait for Java 27 (2025-09, next LTS)
- You run conservative long-lived systems requiring stable support for 5+ years
- Your team lacks CI/testing infrastructure for frequent JDK upgrades
- Your dependencies are not yet verified on Java 25
- You need preview features to stabilize before committing
Practical Upgrade Path
Step 1: Assess Current State
# Check your current Java version
java -version
# List all JDK installations
/usr/libexec/java_home -V # macOS
java -XshowSettings:properties -version 2>&1 | grep java.home # All platforms
Step 2: Test on Java 25 in CI
# Run your entire test suite on Java 25
java -version # Verify JDK 25 is active
mvn clean test # Maven
gradle test # Gradle
# Capture deprecation warnings
javac -Xlint:deprecation src/main/java/**/*.java
Step 3: Fix Deprecations and Removals
Common migrations from Java 21 → 25:
Replace HttpURLConnection:
// Old (Java 8+, deprecated):
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
// Modern (Java 11+):
HttpClient client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder().GET().uri(URI.create(url)).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
Replace java.util.Date with java.time:
// Old:
Date now = new Date();
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 1);
// Modern:
LocalDateTime now = LocalDateTime.now();
LocalDateTime tomorrow = now.plusDays(1);
Replace POJOs with Records:
// Old:
public class User {
private final String id, name;
public User(String id, String name) { this.id = id; this.name = name; }
public String getId() { return id; }
public String getName() { return name; }
@Override public boolean equals(Object o) { /* ... */ }
@Override public int hashCode() { /* ... */ }
}
// Modern (Java 16+):
public record User(String id, String name) {}
Step 4: Enable Preview Features Safely
If your application uses preview features, compile and test with --enable-preview:
javac --enable-preview --release 25 src/main/java/**/*.java
java --enable-preview -cp target/classes MyApplication
But do not use preview features in production unless you plan to upgrade within 6 months.
Step 5: Performance Testing
Run realistic load tests to compare Java 21 and Java 25:
# With Java 21
java -Xmx4g -XX:+UseG1GC MyApplication &
# Run load tests, capture metrics
# With Java 25
java -Xmx4g -XX:+UseG1GC MyApplication &
# Run same load tests, compare
Compare:
- Throughput (requests/second)
- Latency (p50, p99, p99.9)
- Memory usage and GC pause times
- CPU utilization
Step 6: Canary Deployment
- Deploy to a small percentage of production traffic (5-10%)
- Monitor error rates, latency, and memory for 24+ hours
- If stable, increase traffic gradually
- Maintain rapid rollback capability
# Example: Route 10% of traffic to Java 25 instances
# (Kubernetes, load balancer, or feature flags)
Decision Matrix
| Situation | Recommendation | Rationale |
|---|---|---|
| On Java 21 LTS, stable codebase | Stay on 21, upgrade to 27 | Security patches sufficient; 27 (next LTS) in 2025-09 |
| On Java 21, need Virtual Threads now | Upgrade to 25 | VT maturity justifies interim non-LTS; plan for 27 upgrade |
| On Java 11/17, fast-moving services | Jump to 25 | Multiple feature releases and optimizations since 11/17 |
| On Java 8, legacy monolith | Plan for 21 LTS | 21 offers stable, long-support baseline; 25 adds little over 21 |
| Early adopter, microservices | Go to 25, plan for 27 | Experiment with latest features; upgrade every 6mo |
Long-Term Support Strategy
Conservative Approach (Enterprise):
- Baseline on LTS versions (21, 27, 33, ...)
- Upgrade every 3 years on LTS boundaries
- Use 5+ years of security patch support per LTS
Agile Approach (Fast-Moving Services):
- Baseline on current feature release (25 today, 26 in March, 27 in September)
- Upgrade every 6 months with minor code changes
- Automate CI/CD for rapid testing and deployment
Hybrid Approach:
- Core infrastructure on LTS (21)
- Microservices on latest feature release (25)
- Coordinate upgrades quarterly
Troubleshooting Common Issues
"Module not found" after upgrade
Internal JDK APIs were removed. Use jdeps to identify dependencies:
jdeps -jdkinternals myapp.jar
Migrate to public APIs or FFM for native code.
Performance regression on Java 25
Enable detailed GC logging to diagnose:
java -Xlog:gc*:file=gc.log MyApplication
# Analyze gc.log; compare GC tuning between versions
Dependency incompatibilities
Check if your frameworks/libraries are certified for Java 25:
- Spring Framework 6.1+
- Hibernate 6.4+
- Quarkus 3.5+ Update dependencies before upgrading the JDK.
Summary
- Java 25 is a non-LTS feature release; excellent for adopting new capabilities
- Upgrade if you need virtual threads, latest API features, or modern performance improvements
- Wait for Java 27 LTS if you prioritize long-term stability and support
- Test thoroughly on Java 25 in CI before production deployment
- Plan for Java 27 as your next LTS baseline by September 2025