Java mascot "Duke" with binoculars

The unfinished features of Java 25

,

Java 25 will be released on September 16, 2025, and the features have been fixed since the final release candidate on August 21, 2025. This article presents the functions that have not yet been finalized: Preview, Incubator and Experimental.

Unfinalized features

The following describes the features that are still in the development stage but are already included as a preview in the Java 25 JDK. These must be explicitly activated with the corresponding command line parameters and are used to obtain feedback from the Java community. Productive use of these features is not recommended, as they could still change or even disappear from the JDK.

JEP-470 - PEM Encodings of Cryptographic Objects (Preview)

The PEM format (Privacy-Enhanced Mail) is a widely used Base64-based text format for cryptographic keys, certificates and other security-relevant data. As part of JEP-470, an easy-to-use API is to be introduced with which private and public keys, certificates and certificate revocation lists can be converted into PEM format, and cryptographic artifacts stored in PEM format can be converted into corresponding Java objects:

// Zertifikat im DER-Format einlesen
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) cf.generateCertificate(new FileInputStream("/tmp/certificate.der"));

// ins PEM-Format kodieren
PEMEncoder pemEncoder = PEMEncoder.of();
String pem = pemEncoder.encodeToString(certificate);
System.out.println(pem);

// PEM-Format einlesen
PEMDecoder pemDecoder = PEMDecoder.of();
DEREncodable decoded = pemDecoder.decode(pem);
switch (decoded) {
    case X509Certificate x509cert -> System.out.println(x509cert.getSubjectX500Principal());
    case PrivateKey privateKey -> System.out.println(privateKey);
    case PublicKey publicKey -> System.out.println(publicKey);
    // ...
    default -> throw new IllegalStateException("Unexpected value: " + decoded);
}

The methods of the class PEMDecoder provide instances of the interface DEREncodable back. These can then be used, for example, with pattern matching via instanceof or switch can be processed further (-> to the JEP-470).

JEP-502 - Stable Values (Preview)

With final fields previously had to be instantiated directly during declaration, in the constructor or in static class initializers. This can lead to a slower start of the application, as well as to unnecessary object instantiations - such as loggers in a class that are never needed if they do not log anything.

A workaround for this has so far been to create corresponding objects with null and only initialize them directly before the first use. However, the disadvantages of this procedure are that the keyword final is then no longer possible and problems can arise with concurrency.

To counteract this, the StableValues API is to be introduced:

// Bisher:
private Logger logger = null;

// Neu mit StableValue:
private final StableValue<Logger> loggerSv = StableValue.of();

private Logger logger() {
    return loggerSv.orElseSet(() -> {
        System.out.println("Creating logger");
        return Logger.getLogger(getClass().getName());
    });
}

public void submitOrder(User user, List<Product> products) {
    logger().info("order started");
    // ..
    logger().info("order submitted");
}

The orElseSet()-method guarantees that the passed supplier lambda is only executed exactly once, even in the case of concurrency.

An alternative is to get a supplier via the StableValue API:

private final Supplier<Logger> logger = StableValue.supplier(() -> {
    System.out.println("Creating logger");
    return Logger.getLogger(getClass().getName());
});

void submitOrder(User user, List<Product> products) {
    logger.get().info("order started");
    // ..
    logger.get().info("order submitted");
}

Here, too, it is ensured that the supplier lambda is only called once, as StableValue.supplier(...) returns a supplier that contains the object at the first call of get() generated and stored, and for the following get()-calls returns the cached object. The advantage of this variant is that the initialization code is directly in the declaration (-> to the JEP-502).

JEP-505 - Structured Concurrency (Fifth Preview)

With Structured Concurrency (first introduced by me here ), the aim is to create an approach for concurrent programming in which the natural relationship between tasks and subtasks is retained, resulting in more readable, maintainable and reliable concurrent code. Subtasks are processed as part of a task. The superordinate task waits for the results and monitors errors in the individual subtasks.

This fifth preview is accompanied by some changes to the API. For example, a StructuredTaskScope is now opened with a static factory method instead of a constructor:

public Result handleRequest() throws InterruptedException {
    try (var scope = StructuredTaskScope.open()) {
        Subtask<String> user = scope.fork(() -> findUser());
        Subtask<Integer> order = scope.fork(() -> fetchOrder());

        scope.join(); // Join subtasks, propagating exceptions

        return new Result(user.get(), order.get());
    } catch (StructuredTaskScope.FailedException e) {
        // exception handling
    }
}

The open()-method without parameters causes the standard case in which the system waits for all subtasks to complete successfully, or if a (runtime) exception occurs in a subtask, the others are canceled and the exception is stored in a StructuredTaskScope.FailedException wrapped is propagated further.

The join()-behavior can be modified by adding the open(...)-method is an implementation of the StructuredTaskScoped.Joiner-interfaces. The interface provides methods for ready-made joiners, such as anySuccessfulResultOrThrow()which returns the first successful result, or throws an exception if no subtask was successful. If the provided joiners are not sufficient, you can also create your own joiner implementations (-> to the JEP-505).

JEP-507 - Primitive Types in Patterns, instanceof and switch (Third Preview)

Third preview of this feature without changes compared to the previous previews. So far supports pattern matching in the context of records, instanceof and switch no native data types. This is set to change with this JEP.

long myLong = 128;
switch (myLong) {
    case long l when l == 1       -> System.out.println("One");
    case long l when l == 2       -> System.out.println("Two");
    case 10_000_000_000L          -> System.out.println("Ten billion");
    case byte b                   -> System.out.printf("Byte b=%d %n", b);
    case int i when i < 1_000_000 -> System.out.printf("Less than 1 Mio: %d %n", i);
    case long l                   -> System.out.printf("x=%d %n", l);
}

Until now int is the only primitive data type that can be used in switch-instructions was allowed. Now all primitive types work. The special feature: the case distinction does not checkwhether the after case named data type with the actual data type of the variable myLong matches (long), but whether a lossless conversion into the type of case-Condition is possible.
In the example, the value 128 can easily be changed to int and therefore the output reads:

Less than 1 Mio: 128

(Remember, the new switch-variant with arrows instead of colons has no fallthrough. This means that no further cases are checked after the first match).

The test behaves in the same way for instanceof:

long myNum = 125;
if (myNum instanceof byte b) {
    System.out.println("byte: " + b);
}
if (myNum instanceof int i) {
    System.out.println("int: " + i);
}
if (myNum instanceof double d) {
    System.out.println("double: " + d);
}

The issue is:

byte: 125
int: 125
double: 125.0

Since 125 loss-free to byteint and double can be converted, all blocks of the three if-instructions are executed.

Exactly the same output would be given if the myNum for a float with the value 125.0f, as a lossless conversion would also be possible here. With a float with value 125.1f, on the other hand, the output would only be:

double: 125.1

This is because lossless conversion can only take place in the latter case; in the first two cases, the decimal place would be lost.
Finally, an example of how primitive data types can be extracted from a record via a pattern:

record Point(long x, long y) { }
// ...
Point point = new Point(0, 0);
switch (point) {
    case Point(int x, int y)
        when x == 0 && y == 0 -> System.out.println("The center");
    case Point(long x, long y) -> System.out.printf("Point at x=%d, y=%d %n", x, y);
}

Issue:

The center

-> to the JEP-507

JEP-508 - Vector API (Tenth Incubator)

With the Vector API, Java introduces cross-platform support for the development of data-parallel algorithms. Using a "Single Instruction Multiple Data" (SIMD) model, the Vector instructions of different CPU architectures are to be supported. Use cases for this include image and video processing. This tenth incubator preview includes an API adjustment and two implementation changes. The Vector API will remain in incubator status until the necessary features from the Valhalla project are available as a preview.

As this is a specialized area that is unlikely to be relevant to the general public, readers who are interested should take a look at the details for themselves, e.g. on the corresponding description page of the -> JEP-508.

JEP-509 - JFR CPU-Time Profiling (Experimental)

The Java Flight Recorder (JFR) is used for profiling and monitoring the JDK. The JFR already offers good support for profiling memory usage, but there is still room for improvement in CPU profiling.

A timer was added to the Linux kernel in version 2.6.12, which outputs signals at fixed intervals of CPU time instead of at intervals of elapsed real time. This makes it possible to measure CPU cycle consumption accurately and precisely.

The JFR will be improved by using the Linux kernel timer to create more accurate CPU time profiles for Java programs. This functionality is only provided for Linux and is marked as experimental in order to incorporate feedback from the Java developer community. Other platforms may be supported in the future.

A text profile of the particularly busy CPU methods, i.e. those that consume many CPU cycles in their own body and not in calls to other methods, can be created as follows:

$ jfr view cpu-time-hot-methods profile.jfr

-> to the JEP-509

Outlook

The final new features will follow shortly in a separate blog post.

Stefan Waldmann

About ME

Stefan Waldmann has a degree in business informatics and works at doubleSlash as Senior Software Engineer and Lead Developer. In his many years of IT project experience, he has worked, for example, with the Deutsche Telekom AG and the German Post worked together. Among other things, he is an expert in system integration and interfaces as well as Unit testing, test-driven development and software quality. Stefan Waldmann is familiar with many different enterprise software architectures and has extensive experience in the design, integration, development and project management of Java EE projects.

All contributions from Stefan Waldmann

Learn more

Further information on our website and in our newsletter

Arrow up