Introduction
Java 25 is a Long-Term Support (LTS) version of Java that reached General Availability on September 16, 2025. As an LTS release, it provides a stable foundation for enterprise applications and will receive long-term updates from the Java ecosystem.
This version continues the evolution started in recent releases such as Java 17 and Java 21, focusing on:
- Improving concurrency
- Simplifying language constructs
- Enhancing stream processing
- Strengthening the module system.
In this article, you will learn some of the most important features introduced in JDK 25.
1. Scoped Values
After being previewed since JDK 21, Scoped Values are now final in Java 25. They provide a means to share immutable data safely and efficiently within a thread, offering a compelling alternative to ThreadLocal. Unlike ThreadLocal, which creates a per-thread copy, Scoped Values are shared, reducing memory overhead, especially when used with many virtual threads.
A scoped value is bound for a specific context, and that binding is available only during the execution of a run method. This is particularly powerful when combined with Structured Concurrency, as child tasks automatically inherit the parent’s scoped values.
import java.lang.ScopedValue;
public class ScopedValueExample {
private static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
public static void main(String[] args) {
ScopedValue.where(CURRENT_USER, "John").run(ScopedValueExample::handleRequest);
}
private static void handleRequest() {
System.out.println("Current user: " + CURRENT_USER.get());
}
}
This example demonstrates binding the value “John” to the USER scoped value within a specific scope, which is then accessible in the handleRequest method.
Read more about Scoped Values (JEP 506).
2. Module Import Declarations
To simplify the use of modular libraries, JEP 511 introduces module import declarations. With the syntax import module M;, you can import all the public packages exported by a given module. This is particularly useful for prototyping, exploring new APIs, and writing scripts, as it dramatically reduces the number of import statements needed.
// Instead of multiple on-demand imports:
// import java.util.*;
// import java.util.stream.*;
// import java.util.function.*;
// You can now simply import the whole module:
import module java.base;
public class ModuleImportExample {
public static void main(String[] args) {
// Directly use classes from java.util and java.util.stream
var fruits = List.of("apple", "berry", "citrus");
var map = fruits.stream()
.collect(Collectors.toMap(s -> s.substring(0, 1),
Function.identity()));
System.out.println(map);
}
}
This shows how importing the java.base module provides immediate access to collections and streams APIs without multiple package-level imports.
Read more about Module Import Declarations (JEP 511).
3. Compact Source Files and Instance Main Methods
Building on the “Paving the On-Ramp” efforts, JEP 512 finalizes the ability to run simple Java programs without the traditional class and public static void main(String[] args) boilerplate. A source file can now contain an instance main method without an enclosing class declaration, making Java more approachable for beginners and suitable for scripting.
Before Java 25
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
With Java 25
void main() {
System.out.println("Hello, World!");
}
This three-line program is a complete, runnable Java application. The Java launcher implicitly defines a class for the source file and invokes this instance’s main method. This style is useful for small programs, demos, and learning material. It reduces ceremony while keeping the full language available when needed.
Read more about Compact Source Files and Instance Main Methods (JEP 512).
4. Flexible Constructor Bodies
A longstanding restriction in Java required the first statement in a constructor to be a call to another constructor (super(…) or this(…)). JEP 513 removes this limitation, allowing statements to be placed before the constructor invocation. This enables developers to validate arguments and perform setup logic before calling a superclass constructor, leading to cleaner code and preventing bugs where a superclass constructor might inadvertently access uninitialized fields of the subclass.
class Employee extends Person {
private final String officeID;
Employee(String name, int age, String officeID) {
// Prologue: Validation before super() call - fails fast!
if (age < 18 || age > 67) {
throw new IllegalArgumentException("Invalid age for employee");
}
// Safely initialize subclass field before superclass constructor runs
this.officeID = officeID;
// Epilogue: Now invoke the superclass constructor
super(name, age);
}
}
In this example, the Employee constructor validates the age and initializes its own field officeID before calling the Person constructor, ensuring the object’s integrity from the start
This makes constructors easier to read and reduces the need for helper constructors used only for preprocessing.
Read more about Flexible Constructor Bodies (JEP 513).
5. Key Derivation Function API
Java 25 introduces a new standard API in the javax.crypto package for Key Derivation Functions (KDFs). KDFs are cryptographic algorithms used to derive one or more secret keys from a master secret, password, or other input material. This API, which includes a built-in implementation of the widely-used HKDF (HMAC-based Extract-and-Expand KDF), is a crucial step toward making Java ready for post-quantum cryptography by enabling schemes like Hybrid Public Key Encryption (HPKE).
// Create a KDF object for the HKDF algorithm with SHA-256
KDF hkdf = KDF.getInstance("HKDF-SHA256");
// Build the parameters for an extract-then-expand operation
byte[] initialKeyMaterial = "shared-secret".getBytes();
byte[] salt = "random-salt".getBytes();
byte[] info = "context".getBytes();
AlgorithmParameterSpec params =
HKDFParameterSpec.ofExtract() // Start the extraction phase
.addIKM(initialKeyMaterial)
.addSalt(salt)
.thenExpand(info, 32); // Expand to 32 bytes for an AES key
// Derive a new AES key
SecretKey aesKey = hkdf.deriveKey("AES", params);
// Show result
System.out.println("Derived key: " + Arrays.toString(aesKey.getEncoded()).substring(0, 30) + "...");
This code snippet illustrates deriving a 32-byte AES key from an initial key material using the new KDF and HKDFParameterSpec APIs.
Read more about Key Derivation Function API (JEP 510).
6. Compact Object Headers
Every Java object contains a header used by the JVM for metadata such as locking and identity information. Java 25 introduces Compact Object Headers, which reduce the memory footprint of objects in many cases.
This is mainly a JVM-level improvement, so there is no new Java language syntax for it. However, applications that create a large number of objects can benefit from lower memory usage. Learn more about how the memory is allocated in Java.
A JVM option can be used to enable the feature where appropriate:
-XX:+UseCompactObjectHeaders
Compact Object Headers are especially interesting for memory-sensitive services and large heap workloads.
Read more about Compact Object Headers (JEP 519).
7. Generational Shenandoah
Shenandoah is a low-pause garbage collector. Java 25 introduces Generational Shenandoah, which adds a young/old generation design to improve throughput and memory reclamation behavior.
Like other garbage collector improvements, this feature is mostly configured through JVM options rather than ordinary Java syntax.
-XX:+UseShenandoahGC
This change can improve performance characteristics for some applications, especially those with many short-lived objects.
Read more about Generational Shenandoah (JEP 521).
8. Launch Multi-File Source-Code Programs
Finalized in JDK 22, this feature allows you to run Java programs that span multiple source files directly, without compiling them with javac. The Java launcher can now handle a program whose main class references other classes defined in the same directory. This is a huge productivity boost for small to medium-sized projects, learning exercises, and experimentation.
Directory Structure
./demo-multi-file-source/
├── Hello.java
└── Greeting.java
File Greeting.java
public class Greeting {
public static String get() {
return "Hello from another file!";
}
}
File Hello.java
void main() {
System.out.println(Greeting.get());
}
Running Without Compilation
Navigate to the demo-multi-file-source directory and run:
java Main.java
Output:
Hello from another file!
Key Points
- No explicit compilation needed – The Java launcher handles compilation automatically
- All files must be in the same directory (or in a directory structure that mirrors package declarations)
- Only the main file needs to be specified – The launcher finds dependencies automatically
- Works with instance main methods – As shown with void
main()above
Read more about Launch Multi-File Source-Code Programs.
9. Preview & Incubating
A preview feature is a fully specified language or JVM feature that is made available for developers to experiment with before it becomes permanent in a future Java release. The goal is to gather real-world feedback from developers. Based on this feedback, the feature may be refined, finalized in a later version, or, in rare cases, removed.
Preview features are disabled by default. To test them, you must explicitly enable them when compiling and running your program. See How to Enable Preview Features in Java for a step-by-step guide.
9.1. Primitive Types in Patterns, instanceof, and switch (JEP 507 – Third Preview)
Pattern matching in Java has been extended to support primitive type patterns in all pattern contexts, including instanceof and switch . This allows for more expressive and safer code when working with primitive values, reducing the need for manual boxing and casting.
static void testPrimitivePattern(Object obj) {
switch (obj) {
case int i -> System.out.println("Handling int: " + i);
case long l -> System.out.println("Handling long: " + l);
case String s -> System.out.println("Handling string: " + s);
case null -> System.out.println("It's null");
default -> System.out.println("Other type");
}
}
This example shows a switch statement that uses primitive type patterns (int i, long l) to match on Integer and Long objects, handling them directly as their primitive counterparts.
Read more about Primitive Types in Patterns, instanceof, and switch (JEP 507 – Third Preview).
9.2. Structured Concurrency (JEP 505 – Fifth Preview)
Structured Concurrency, an incubating feature from Project Loom, continues to evolve. It treats multiple tasks running in different threads as a single unit of work, which helps to streamline error handling and cancellation, improving the reliability and observability of concurrent code. The fifth preview introduces API refinements, such as a new open static factory method for creating task scopes, moving away from constructor use.
try (var scope = StructuredTaskScope.<String>open()) { // Use the new open() method
var userTask = scope.fork(() -> fetchUser());
var orderTask = scope.fork(() -> fetchOrder());
scope.join(); // Wait for both tasks
System.out.println(userTask.get() + " - " + orderTask.get());
} // All forked threads are automatically joined/cancelled here
This example demonstrates creating a StructuredTaskScope with open(), forking two concurrent subtasks and then joining them to process their results. The scope ensures that if one task fails, the other is cancelled automatically.
Read more about Structured Concurrency (JEP 505 – Fifth Preview).
Conclusion
In this article, you learned some of the main features introduced by Java 25.
The most notable additions include:
- Scoped Values for safer contextual data sharing
- Module Import Declarations for simpler module usage
- Compact Source Files and Instance Main Methods for less ceremony
- Flexible Constructor Bodies for cleaner initialization logic
- Key Derivation Function API for standardized cryptographic key derivation
- Compact Object Headers for lower memory usage
- Generational Shenandoah for improved garbage collection behavior
- Launch Multi-File Source-Code Programs for running multiple source files without compilation
Java 25 continues the recent trend of making Java more expressive, more approachable, and more efficient at runtime.
You can find the complete code of this article here on GitHub.
