| name | java-21-to-25 |
| description | Upgrade Java projects from JDK 21 to JDK 25 covering Flexible Constructor Bodies, Stream Gatherers, Class-File API, Scoped Values, Security Manager removal |
GitHub Copilot Instructions: Java 21 to Java 25 Upgrade Guide
Project Context
This guide provides comprehensive GitHub Copilot instructions for upgrading Java projects from JDK 21 to JDK 25, covering new language features, API changes, deprecations, and migration patterns based on 39 JEPs delivered in Java 22, 23, 24, and 25.
Critical Migration: Security Manager Permanently Disabled
JEP 486: Permanently Disable the Security Manager (Java 24)
Migration Pattern: Remove all Security Manager usage
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new FilePermission("/tmp/data", "read"));
}
- Remove all
.policy files and -Djava.security.policy JVM flags
- Remove
System.setSecurityManager() and System.getSecurityManager() calls
- The
java.security.manager system property is ignored
Critical Migration: sun.misc.Unsafe Deprecation and Warnings
JEP 471: Deprecate Memory-Access Methods in sun.misc.Unsafe (Java 23)
JEP 498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe (Java 24)
Migration Pattern: Replace Unsafe with VarHandle or Foreign Memory API
Unsafe unsafe = ;
long offset = unsafe.objectFieldOffset(MyClass.class.getDeclaredField("count"));
unsafe.getInt(obj, offset);
unsafe.putInt(obj, offset, 42);
unsafe.compareAndSwapInt(obj, offset, 0, 1);
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class AtomicCounter {
private volatile int count;
private static final VarHandle COUNT;
static {
try {
COUNT = MethodHandles.lookup()
.findVarHandle(AtomicCounter.class, "count", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
public int get() { return (int) COUNT.get(this); }
public void set(int val) { COUNT.set(this, val); }
public boolean cas(int expected, int newVal) {
return COUNT.compareAndSet(this, expected, newVal);
}
}
import java.lang.foreign.*;
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment = arena.allocate(ValueLayout.JAVA_INT, 10);
segment.set(ValueLayout.JAVA_INT, 0, 42);
int value = segment.get(ValueLayout.JAVA_INT, 0);
}
Critical Migration: JNI Restrictions
JEP 472: Prepare to Restrict the Use of JNI (Java 24)
Migration Pattern: Declare native access and migrate to FFM API
java --enable-native-access=ALL-UNNAMED -jar myapp.jar
java --enable-native-access=com.example.mymodule -jar myapp.jar
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public long getProcessId() {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
MethodHandle getpid = linker.downcallHandle(
lookup.find("getpid").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG)
);
try {
return (long) getpid.invoke();
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
Language Features
JEP 456: Unnamed Variables & Patterns (Java 22)
Migration Pattern: Use _ for unused variables and patterns
try {
} catch (NumberFormatException ex) {
System.out.println("Invalid number");
}
for (Order order : orders) {
total++;
}
try {
} catch (NumberFormatException _) {
System.out.println("Invalid number");
}
for (Order _ : orders) {
total++;
}
if (obj instanceof Point(var x, _)) {
System.out.println("x = " + x);
}
switch (shape) {
case Circle(var radius, _) -> computeCircle(radius);
case Rectangle(var w, var h) -> computeRect(w, h);
default -> throw new IllegalArgumentException();
}
map.forEach((_, value) -> process(value));
JEP 512: Compact Source Files and Instance Main Methods (Java 25)
Migration Pattern: Simplified entry points and compact class declarations
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
void main() {
System.out.println("Hello, World!");
}
String greeting = "Hello";
void main() {
println(greeting + ", World!");
}
JEP 513: Flexible Constructor Bodies (Java 25)
Migration Pattern: Statements before super() or this()
public class PositiveInteger extends Number {
private final int value;
public PositiveInteger(int value) {
super();
if (value <= 0) throw new IllegalArgumentException("Must be positive");
this.value = value;
}
}
public class PositiveInteger extends Number {
private final int value;
public PositiveInteger(int value) {
if (value <= 0) {
throw new IllegalArgumentException("Must be positive: " + value);
}
this.value = value;
super();
}
}
public class NamedLogger extends AbstractLogger {
public NamedLogger(String rawName) {
var normalized = rawName.strip().toLowerCase();
if (normalized.isEmpty()) {
throw new IllegalArgumentException("Name cannot be blank");
}
super(normalized);
}
}
JEP 511: Module Import Declarations (Java 25)
Migration Pattern: Import all packages exported by a module
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.function.Function;
import module java.base;
import module java.sql;
import module java.net.http;
import module java.base;
void main() {
var list = List.of(1, 2, 3);
var now = Instant.now();
var path = Path.of("/tmp/test.txt");
}
JEP 454: Foreign Function & Memory API (Java 22)
Migration Pattern: Replace JNI with the standard FFM API
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public long strlen(String s) {
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlenHandle = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateFrom(s);
return (long) strlenHandle.invoke(cString);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
try (Arena arena = Arena.ofConfined()) {
MemorySegment point = arena.allocate(
MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")
)
);
point.set(ValueLayout.JAVA_INT, 0, 10);
point.set(ValueLayout.JAVA_INT, 4, 20);
}
JEP 506: Scoped Values (Java 25)
Migration Pattern: Replace ThreadLocal with ScopedValue
private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>();
public void handleRequest(User user) {
CURRENT_USER.set(user);
try {
processRequest();
} finally {
CURRENT_USER.remove();
}
}
public void processRequest() {
User user = CURRENT_USER.get();
}
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
public void handleRequest(User user) {
ScopedValue.runWhere(CURRENT_USER, user, () -> {
processRequest();
});
}
public void processRequest() {
User user = CURRENT_USER.get();
}
String result = ScopedValue.callWhere(CURRENT_USER, user, () -> {
return generateReport();
});
JEP 485: Stream Gatherers (Java 24)
Migration Pattern: Custom intermediate stream operations
import java.util.stream.Gatherers;
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3))
.toList();
List<List<Integer>> chunks = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowFixed(2))
.toList();
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.fold(() -> 0, Integer::sum))
.toList();
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.scan(() -> 0, Integer::sum))
.toList();
List<String> results = urls.stream()
.gather(Gatherers.mapConcurrent(10, url -> fetch(url)))
.toList();
Gatherer<String, ?, String> dedup = Gatherer.ofSequential(
() -> new Object() { String last = null; },
(state, element, downstream) -> {
if (!element.equals(state.last)) {
state.last = element;
return downstream.push(element);
}
return true;
}
);
Stream.of("a", "a", "b", "b", "b", "c", "a")
.gather(dedup)
.toList();
JEP 484: Class-File API (Java 24)
Migration Pattern: Replace ASM with standard Class-File API
import java.lang.classfile.*;
import java.lang.classfile.attribute.*;
ClassModel classModel = ClassFile.of().parse(classBytes);
String className = classModel.thisClass().asInternalName();
for (MethodModel method : classModel.methods()) {
String name = method.methodName().stringValue();
String descriptor = method.methodType().stringValue();
}
byte[] transformedBytes = ClassFile.of().transform(classModel,
ClassTransform.transformingMethods(
MethodTransform.transformingCode(
(builder, element) -> {
if (element instanceof CodeElement ce) {
builder.with(ce);
}
}
)
)
);
byte[] newClass = ClassFile.of().build(
ClassDesc.of("com.example", "Generated"),
classBuilder -> classBuilder
.withFlags(ClassFile.ACC_PUBLIC)
.withMethod("hello", MethodTypeDesc.of(ClassDesc.ofDescriptor("V")),
ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
methodBuilder -> methodBuilder.withCode(codeBuilder -> codeBuilder
.getstatic(ClassDesc.of("java.lang", "System"), "out",
ClassDesc.of("java.io", "PrintStream"))
.ldc("Hello from generated class!")
.invokevirtual(ClassDesc.of("java.io", "PrintStream"), "println",
MethodTypeDesc.of(ClassDesc.ofDescriptor("V"),
ClassDesc.of("java.lang", "String")))
.return_()
)
)
);
JEP 467: Markdown Documentation Comments (Java 23)
Migration Pattern: Convert HTML JavaDoc to Markdown
JEP 458: Launch Multi-File Source-Code Programs (Java 22)
Migration Pattern: Run multi-file programs directly without compilation
java Main.java
java --source 25 com/example/Main.java
JEP 491: Synchronize Virtual Threads without Pinning (Java 24)
Migration Pattern: synchronized blocks no longer pin virtual threads
public class SharedResource {
private final Object lock = new Object();
public void access() {
synchronized (lock) {
performBlockingIO();
}
}
}
I/O and Networking Improvements
JEP 493: Linking Run-Time Images without JMODs (Java 24)
Migration Pattern: Create smaller custom runtime images
jlink --module-path $JAVA_HOME/jmods \
--add-modules java.base,java.sql \
--output custom-runtime
jlink --add-modules java.base,java.sql \
--output custom-runtime
Security Enhancements
JEP 496: Quantum-Resistant ML-KEM (Java 24)
JEP 497: Quantum-Resistant ML-DSA (Java 24)
Migration Pattern: Use quantum-resistant cryptographic algorithms
import javax.crypto.KEM;
import java.security.KeyPairGenerator;
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM");
kpg.initialize(NamedParameterSpec.ML_KEM_768);
var keyPair = kpg.generateKeyPair();
KEM kem = KEM.getInstance("ML-KEM");
KEM.Encapsulator enc = kem.newEncapsulator(keyPair.getPublic());
KEM.Encapsulated encapsulated = enc.encapsulate();
byte[] ciphertext = encapsulated.encapsulation();
SecretKey sharedSecret = encapsulated.key();
KEM.Decapsulator dec = kem.newDecapsulator(keyPair.getPrivate());
SecretKey receiverSecret = dec.decapsulate(ciphertext);
import java.security.Signature;
KeyPairGenerator sigKpg = KeyPairGenerator.getInstance("ML-DSA");
sigKpg.initialize(NamedParameterSpec.ML_DSA_65);
var sigKeyPair = sigKpg.generateKeyPair();
Signature sig = Signature.getInstance("ML-DSA");
sig.initSign(sigKeyPair.getPrivate());
sig.update(data);
byte[] signature = sig.sign();
sig.initVerify(sigKeyPair.getPublic());
sig.update(data);
boolean valid = sig.verify(signature);
JEP 510: Key Derivation Function API (Java 25)
Migration Pattern: Standard API for deriving cryptographic keys
import javax.crypto.KDF;
KDF hkdf = KDF.getInstance("HKDF-SHA256");
SecretKey derived = hkdf.deriveKey("AES",
KDF.HKDFParameterSpec.ofExtract()
.addIKM(inputKeyMaterial)
.addSalt(salt)
.thenExpand(info, 32)
);
JVM and Performance Improvements
JEP 519: Compact Object Headers (Java 25)
Migration Pattern: Reduced object memory overhead (no code changes needed)
AOT Startup Optimizations
JEP 483: Ahead-of-Time Class Loading & Linking (Java 24)
JEP 515: Ahead-of-Time Method Profiling (Java 25)
JEP 514: Ahead-of-Time Command-Line Ergonomics (Java 25)
Migration Pattern: Dramatically improve startup time with AOT caching
java -XX:AOTCacheOutput=app.aot -cp myapp.jar com.example.Main
java -XX:AOTCache=app.aot -cp myapp.jar com.example.Main
Garbage Collection Updates
JEP 474: ZGC Generational Mode by Default (Java 23)
JEP 490: ZGC Remove Non-Generational Mode (Java 24)
-XX:+UseZGC -XX:+ZGenerational
-XX:+UseZGC
JEP 423: Region Pinning for G1 (Java 22)
JEP 475: Late Barrier Expansion for G1 (Java 24)
JEP 521: Generational Shenandoah (Java 25)
-XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational
JFR Enhancements
JEP 518: JFR Cooperative Sampling (Java 25)
JEP 520: JFR Method Timing & Tracing (Java 25)
Build System Configuration
Maven Configuration
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>25</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<argLine>--enable-native-access=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>
</build>
Gradle Configuration
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
tasks.withType<JavaCompile> {
options.release.set(25)
}
tasks.withType<Test> {
useJUnitPlatform()
jvmArgs("--enable-native-access=ALL-UNNAMED")
}
Deprecations and Removals
Removed: 32-bit x86 Ports
- JEP 479: Windows 32-bit x86 port removed in Java 24
- JEP 503: Linux 32-bit x86 port removed in Java 25
- Action: Ensure deployments use 64-bit JDK
JEP 501: Deprecate 32-bit x86 Port for Removal (Java 24)
- Remaining 32-bit x86 ports deprecated — plan for 64-bit migration
Testing and Migration Strategy
Phase 1: Build Compatibility (Weeks 1-2)
-
Update build system
- Set compiler release to 25
- Update Maven/Gradle plugins
- Update CI/CD pipelines
-
Address breaking changes
- Remove all SecurityManager usage (JEP 486)
- Add
--enable-native-access for JNI usage (JEP 472)
- Remove
-XX:-ZGenerational flags (JEP 490)
Phase 2: Deprecation Warnings (Weeks 3-4)
-
Fix sun.misc.Unsafe usage (JEP 471/498)
- Replace with VarHandle for on-heap access
- Replace with Foreign Memory API for off-heap access
- Update third-party libraries
-
Update JNI code (JEP 472)
- Grant native access permissions
- Evaluate FFM API migration for new code
Phase 3: Adopt Standard Features (Weeks 5-6)
-
Language features
- Use unnamed variables
_ (JEP 456)
- Adopt flexible constructor bodies (JEP 513)
- Use module imports (JEP 511)
- Adopt compact source files where appropriate (JEP 512)
- Convert HTML JavaDoc to Markdown (JEP 467)
-
API features
- Use Stream Gatherers (JEP 485)
- Replace ASM with Class-File API (JEP 484)
- Adopt Scoped Values (JEP 506)
- Replace ThreadLocal where appropriate
Phase 4: Performance Optimization (Weeks 7-8)
-
Startup optimization
- Set up AOT caching (JEP 483/514/515)
- Benchmark startup improvements
-
GC tuning
- Test generational ZGC (now the only mode)
- Evaluate generational Shenandoah (JEP 521)
- Benefit from compact object headers (JEP 519)
- Leverage virtual thread improvements (JEP 491)
-
Security updates
- Evaluate quantum-resistant algorithms (JEP 496/497)
- Use KDF API for key derivation (JEP 510)
Best Practices
-
Use Unnamed Variables Aggressively
- In catch blocks, enhanced for loops, lambdas, and patterns
- Improves code clarity by signaling intentionally unused values
-
Adopt Scoped Values over ThreadLocal
- Especially important for virtual thread workloads
- Immutable, bounded lifetime, no leak risk
-
Embrace Stream Gatherers
- Replace complex collector chains with
gather()
- Use built-in gatherers:
windowSliding, windowFixed, fold, scan, mapConcurrent
-
Plan for Post-Quantum Cryptography
- Start testing ML-KEM and ML-DSA with non-production workloads
- Quantum computers that could break RSA/ECC are expected within a decade
-
Set Up AOT Caching for Production
- Single-flag setup in Java 25 makes this easy
- Significant startup improvements with zero code changes
-
Migrate Off sun.misc.Unsafe Now
- Runtime warnings in Java 24 signal upcoming removal
- VarHandle and FFM API are the supported replacements
This comprehensive guide enables GitHub Copilot to provide contextually appropriate suggestions when upgrading Java 21 projects to Java 25, focusing on language enhancements, API improvements, security updates, and modern Java development practices.