This is a cache of https://developer.ibm.com/articles/java-whats-new-java25/. It is a snapshot of the page as it appeared on 2025-12-22T12:24:09.211+0000.
What's new in Java 25 for developers - IBM Developer

Article

What's new in Java 25 for developers

Review the stable and preview features in this new LTS release of Java

Java 25 was released on September 16, 2025. This version is important because it is an LTS (Long Term Support) version, the next one after Java 21. As an LTS release, it will receive a minimum of five years of support and updates/bug fixes.

This new version contains multiple enhancements, both for devs and for the runtime execution.

In this article, we'll focus on stable and preview features for developers. We won't cover incubator features as they will likely change in future releases. (The Vector API remains in the incubator stage for the tenth time, waiting for the release of Project Valhalla.)

Major features and enhancements

These are some of the most significant additions, changes, and previews in Java 25:

FeatureWhat it does
JEP 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)You can use primitive types in pattern matching, instanceof, and switch constructs.
JEP 511: Module Import DeclarationsA module import lets you access every class from the packages that the module exports.
JEP 512: Compact Source Files and Instance Main MethodsYou can write a main method without needing to create a class.
JEP 513: Flexible Constructor Bodiessuper and this statements do not have to be the first statement in a constructor. This is better, for example, for the argument validation.
Scoped Values (JEP 506)A simpler way to share immutable variables across a thread and its child threads.
Key Derivation Function API (JEP 510)Provides standard APIs in the JDK for deriving cryptographic keys (PBKDF2, Argon2, etc.).
JEP 519: Compact Object HeadersReduces the size of object headers (metadata per object) to make objects use less memory.
JEP 521: Generational Shenandoah Garbage CollectorMakes the Shenandoah GC use a generational model (young/old generations), improving GC performance especially in startup or mixed-allocation programs.
JFR EnhancementsImprovements to JDK Flight Recorder, like CPU-time profiling, cooperative sampling, method timing, and tracing.
Stable Values (JEP 502, Preview)Provides a mechanism to define values that are set once and then treated as constants by the JVM, allowing optimizations beyond what final fields offer.
PEM Encodings for Cryptographic Objects (JEP 470, Preview)Supports encoding and decoding cryptographic keys, certificates in PEM format.

New stable features for developers

These include:

  • Module import declarations
  • Flexible constructor bodies
  • Scoped values
  • Key derivation function (KDF) API

Module import declarations

Since the early years of Java, you needed to import the classes you were going to use, except the ones in the java.lang package (such as String, Integer, and so on).

Java 9 introduced the Java Platform Module System (JPMS) as a way to organize code into modules. It let you group packages into modules, declare what they export, and specify what they require. Even the JDK itself was modularized into about 90+ modules, covering both the standard Java SE API and some JDK-specific tools.

For example, the java.base module contains fundamental packages like java.lang, java.util, java.io, java.net, java.time, and java.math, or the java.sql module contains java.sql and javax.sql packages.

If you are interested in getting the list of modules, you can use the java --list-modules command from the terminal. To see which packages are inside a module, you can use the java --describe-module <module> command.

After this brief yet necessary introduction to Java modules, let's explore the module import declarations feature.

A module import lets you use all classes from the exported packages of a module, without having to import them explicitly.

The syntax is import module, and you must place the statement in the import section of a Java class.

Let's see a simplified JDBC code example without using the module import feature:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class PersonRepository {
    public void insertPerson(Person p) {

        try (Connection conn = DriverManager.getConnection(url, user, password)) {

            // Insert data using PreparedStatement
            String insertSql = "INSERT INTO users (name, email) VALUES (?, ?)";
            try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
 ...
 }
 } catch (SQLException e) {
            e.printStackTrace();
 }
 }
}

There is nothing wrong with this code, but as the name suggests, this class will only contain classes from the SQL package. So the import section could be simplified to:

import module java.sql;

public class PersonRepository {
    public void insertPerson(Person p) {

        try (Connection conn = DriverManager.getConnection(url, user, password)) {

            // Insert data using PreparedStatement
            String insertSql = "INSERT INTO users (name, email) VALUES (?, ?)";
            try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
 ...
 }
 } catch (SQLException e) {
            e.printStackTrace();
 }
 }
}

In the example above, you don’t need to import any java.sql classes as all of them are automatically imported.

Two important things about module import declarations:

  • In the case of importing modules with classes having the same name, for example java.sql.Date and java.util.Date, then you need to also add explicitly the required class as a regular import.

    import module java.base;
      import module java.sql;
    
      import java.sql.Date;
  • When a module depends on another with requires transitive, any module that uses the first one automatically gets access to the second one’s exported packages too.

Module import declarations are a good addition to avoid boilerplate code in import sections.

Flexible constructor bodies

In Java, constructors traditionally could not include custom code before invoking super(…) or this(…). This is not a hard limitation, but in certain cases, you might end up creating code that is harder to read or less performant, as you execute some object creation statements when they are not required.

With Java 25, you can add statements before super or this, enabling:

  • Validation of parameters before the construction of the parent objects
  • Calculating local variables
  • Initializing fields

Let's see an example of a class Person that extends from LivingBeing. One of the attributes that every living being can have is age, whether it is an adult or not. However, depending on the living being, adulthood is reached at a different age. For example, in humans, we can consider 18 years old to be the age of adulthood.

Let's implement these classes using the new flexible constructor bodies feature.

First, the base class:

public class LivingBeing {

    private int age;
    private boolean adult;

    public LivingBeing(int age, boolean adult) {
        this.age = age;
        this.adult = adult;
 }

}

In the above class, nothing new, but let's implement the Person class:

public class Person extends LivingBeing {

    public Person(int age) {

        if (age < 0) throw new IllegalArgumentException();
        boolean isAdult = age > 17;

        super(age, isAdult);
 }
}

In Java 25, you can do the verification of the parameters and the initialization of the isAdult variable before calling super, making the process more performant in case of an error on passing a wrong age value.

Scoped values

Scoped values are a new mechanism in Java that lets you safely share data with a limited scope, especially across threads.

If you have ever used ThreadLocal, you might find the following problems:

  • Data can leak if you forget to clean it up.
  • It’s hard to manage with thread pools.
  • Data can live longer than intended.

Scoped Values fixes these problems by:

  • Defining a scope where variables are valid.
  • Working naturally with virtual threads.
  • Preventing accidental uses like ThreadLocal.

To use it, first you need to instantiate the ScopedValue class:

public final static ScopedValue<String> USER = ScopedValue.newInstance();

Then, you bind a value within a scope using ScopedValue.where(...):

ScopedValue.where(USER, "Alice").run(() -> {
    System.out.println("Hello " + USER.get()); // prints Hello Alice
});

The value is only valid during the thread execution of the run method.

Key derivation function (KDF) API

A Key Derivation function is a cryptographic algorithm used to generate secure keys from a password.

This function is now officially part of the JDK (no need for third-party libraries).

Let's use the HKDF-SHA256 function to derive a key from a passphrase:

KDF hkdf = KDF.getInstance("HKDF-SHA256");
AlgorithmParameterSpec params =
    HKDFParameterSpec.ofExtract()
 .addIKM("the super secret passphrase".getBytes(StandardCharsets.UTF_8))
 .addSalt("the salt".getBytes(StandardCharsets.UTF_8))
 .thenExpand("my derived key description".getBytes(StandardCharsets.UTF_8), 32);

SecretKey key = hkdf.deriveKey("AES", params);

System.out.println("key = " + HexFormat.of().formatHex(key.getEncoded()));

You can use the SecretKey to encrypt some data.

Preview features for developers

These include:

  • Stable values
  • PEM encodings for cryptographic objects
  • Other previews

Stable values

Stables values solves the problem of thread-safe lazy-initialization of variables. Before Java 25, you should have used the double-checked locking idiom approach, which is a complex algorithm.

With stable values, variables are initialized exactly once, at any time, and in a thread-safe way. Then, they are considered immutable.

For example, suppose a factory method returns a unique instance of a particular class. To protect multiple threads from eventually having different instances of the same class, we'll use the StableValue class to create and maintain only one instance, which the application creates by the first thread requiring this instance.

public class ConfigurationFactory {
    private final StableValue<Configuration> configuration = StableValue.of();

    private Configuration getConfiguration() {
        return configuration.orElseSet(new Configuration(...));
 }
}

First time, you access the getConfiguration method, the Configuration class is instantiated and cached into the StableValue instance in a thread-safe way so that multiple threads can call the method concurrently, but the application will create only one instance. The other threads wait until the value is available.

Then, the application will return the cached value every time it is requested.

PEM encodings for cryptographic objects

In Java 25, a new API is introduced for encoding and decoding or converting cryptographic keys and certificates in PEM format.

The main classes for handling PEM are java.security.PEMEncoder and java.security.PEMDecoder.

Let's see how to decode a private key text into a PrivateKey object:

String keyPem = "....";
String passphrase = "supersecret";

PrivateKey privateKey = PEMDecoder.of()
 .withDecryption(passphrase.toCharArray())
 .decode(keyPem, PrivateKey.class);

Similarly, you can decode a PEM certificate:

Certificate cert = PEMDecoder.of()
 .withDecryption(passphrase.toCharArray())
 .decode(pem, X509Certificate.class);

Now, to deal with PEM, you don't need an external library or long code snippets anymore, only three lines of code.

Other previews

There have been two updates on previous preview features that have remained in the preview stage:

  • Structured concurrency (Fifth Preview): Now, you instantiate the StructuredTaskScope using the static StructuredTaskScope.open() method. The new Joiner interface defines the joining strategy of all subtasks.

  • Primitive Types in Patterns, Instanceof, and Switch (Third Preview): Pattern matching or instanceof supports primitive types too. For example, case byte b ->.

Conclusions

Java 25 is the new LTS version of Java with many improvements not only for developers but also at runtime in terms of performance, startup times, or garbage collector, as well as integration with container technology.

We encourage you to move to this new version of Java.