Skip to main content

What Features Can We Use in Java 25?

· 11 min read
Linh Nguyen
T-90MS Main Battle Tank
thumbnail

Java 25 drops on September 25 this year (as of August 18, when I'm writing this). Will enterprises immediately jump on the Java 25 hype train? LOL, nope. They'll probably stick with Java 8 until the heat death of the universe (there are tons of breaking changes from Java 21 to Java 25, so without a solid migration plan, your services will explode spectacularly), but hey, we can still have some fun exploring what's new!

TL;DR

Too lazy to read? I've got you covered:

Still need to read, but less verbose now.

What's New in JDK 21?

Check this dedicated article for more information.

JDK 22

List of JEPs: https://openjdk.org/projects/jdk/22

Foreign Function & Memory API (JEP 454)

Click Here to Go Back to TL;DR

Current status: Finalized in JDK 22.

This JEP is basically Java's way of saying "JNI, it's not you, it's me... actually, no, it IS you." It introduces a shiny new way to talk to code and data outside the Java bubble, replacing the ancient and frankly terrifying JNI. Most of us won't need to touch this beautiful monster, but if you're into that whole "I want blazing performance and I'm not afraid of segfaults" vibe, then buckle up, buttercup!

Unnamed Variables & Patterns (JEP 456)

Click Here to Go Back to TL;DR

Current status: Finalized in JDK 22.

Ever had those awkward moments where you need a variable just to satisfy the compiler's obsessive-compulsive tendencies, but you'll never actually use it? We usually rename them to ignored (thanks, IntelliJ!) or just pretend they don't exist. Sure, it works, but it's about as elegant as wearing socks with sandals, and your code quality tools will absolutely lose their minds.

With JEP 456, you can just slap a _ character (yeah, it kinda looks like a sideways smile 😊 in Markdown) to tell everyone "I need to declare this variable for reasons, but I'm totally ghosting it, and that's intentional."

Take this example:

With JDK 22+, you can just:

list.removeIf(_ -> LocalDateTime.now().isBefore(threshold));

There are various places where you can unleash this JEP. Read the docs for more chaos!

JDK 23

List of JEPs: https://openjdk.org/projects/jdk/23

Markdown JavaDocs (JEP 467)

Click Here to Go Back to TL;DR

Current status: Finalized in JDK 23.

Writing JavaDocs is painful? Well, now you can write them in Markdown! Still painful, but at least you're suffering in style! It's like getting a root canal, but with prettier formatting.

Take this example:

/// Behold the utility class, accompanied by JavaDocs elegantly rendered in Markdown, the undisputed king of blog posts!
///
/// You can even embed code blocks like this:
///
/// ```java
/// // Delicious, JEP 512
/// void main() {
/// IO.println("Hello, World");
/// }
/// ```
public class Utils {

// Your utility methods here (probably all static, let's be honest)
}

Hovering over the Utils class gives us this absolute beauty (proudly presented by IntelliJ):

IntelliJ JavaDocs Render

Your old JavaDocs aren't magically transforming into Markdown (sorry, no fairy godmother here), but expect future docs to bow down to our Markdown overlords while the old HTML-style JavaDocs become digital fossils.

JDK 24

List of JEPs: https://openjdk.org/projects/jdk/25

Stream Gatherers (JEP 485)

You can see this detailed article for more info.

Synchronize Virtual Threads without Pinning (JEP 491)

Click Here to Go Back to TL;DR

Current status: Finalized in JDK 24.

The dreaded pinning drama with the synchronized keyword has been squashed when using Virtual Threads. It only took them... checks calendar ...two entire years! Better late than never, right?

But hold your horses: if you're the type who likes to micromanage synchronization (fairness, timeouts, "who gets to go first" politics), you'll still want to cozy up with the classics like ReentrantLock, Semaphore, or other goodies from the java.util.concurrent.locks treasure chest.

From The JEP 491 Documentation:

The synchronized keyword in the Java programming language is defined in terms of monitors: Every object is associated with a monitor that can be acquired (i.e., locked), held for a time, and then released (i.e., unlocked). Only one thread at a time may hold an object's monitor. For a thread to run a synchronized instance method, the thread first acquires the monitor associated with the instance; when the method is finished, the thread releases the monitor.

To implement the synchronized keyword, the JVM tracks which thread currently holds an object's monitor. Unfortunately, it tracks which platform thread holds the monitor, not which virtual thread. When a virtual thread runs a synchronized instance method and acquires the monitor associated with the instance, the JVM records the virtual thread's carrier platform thread as holding the monitor — not the virtual thread itself.

If a virtual thread were to unmount inside a synchronized instance method, the JDK's scheduler would soon mount some other virtual thread on the now-free platform thread. That other virtual thread, because of its carrier, would be viewed by the JVM as holding the monitor associated with the instance. Code running in that thread would be able to call other synchronized methods on the instance, or release the monitor associated with the instance. Mutual exclusion would be lost. Accordingly, the JVM actively prevents a virtual thread from unmounting inside a synchronized method.

Pro tip: JNI calls will still pin your threads like it's 1999, so keep that performance profiler handy!

Preparing for Our Quantum Overlords: JEP 496 and JEP 497

Click Here to Go Back to TL;DR

TL;DR: These JEPs are Java's insurance policy against the day quantum computers decide to crack all our encryption like it's a piñata at a toddler's birthday party. Java is future-proofing itself so we don't all panic when quantum computers show up and make RSA weep.

The Quantum-Proof Arsenal:
  • JEP 496 brings us ML-KEM (Module-Lattice-Based Key Encapsulation). Think of it as the bodyguard for your secret keys when they're traveling through sketchy internet neighborhoods.

  • JEP 497 delivers ML-DSA (Module-Lattice-Based Digital Signature Algorithm), your new quantum-proof way to say "Yes, this message is really from me and nobody tampered with it."

Both are NIST-approved and ready to laugh in the face of Shor's algorithm. Good to know Java's got our backs when the quantum apocalypse arrives!

Finally, JDK 25

JDK 25 blesses us with these shiny new features (check the dedicated OpenJDK page here for the full flavors):

Compact Source Files and Instance Main Methods (JEP 512)

Click Here to Go Back to TL;DR

Say goodbye to the ancient incantation "public static void main string args". Read this dedicated article for the full history journey.

Module Import Declarations (JEP 511)

Click Here to Go Back to TL;DR

Tired of managing seventeen million import statements like you're collecting Pokemon cards?

With JEP 511, you can now import all public top-level classes from a module with one glorious declaration:

import module java.base;

This simplifies your code and saves your sanity.

Additionally, in compact source files, the java.base module is automatically imported, granting immediate access to commonly used classes like List, Set, and Date without the usual import ceremony.

The complete list of packages bundled within java.base can be seen here.

Flexible Constructor Bodies (JEP 513)

Click Here to Go Back to TL;DR

Tired of this obnoxious error message?

Call to 'super()' must be first statement in constructor body

Good news! As of JDK 25, Java finally allows statements before super(...) or this(...) in constructors. No more juggling validation logic into awkward static helper methods just to appease the compiler gods. Thank you, JEP 513, you beautiful thing!

See this example:

abstract class Member {

private final String name;

protected Member(String name) {
this.name = name;
}
}

And do the validation when instantiating new objects:

Now you can write sensible code:

public ClubMember(String name, int age) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}

if (age < 18) {
throw new IllegalArgumentException("Must be an adult to register!");
}

super(name); // No longer needs to be first!

this.age = age;
}

Still:

Code before super(...) or this(...) may only perform safe operations (validating parameters, assigning fields, computing values), but cannot access the instance under construction (no this, instance methods, or field reads).

The constructor must still call exactly one super(...) or this(...) on all paths.

This change brings long-overdue ergonomics improvements and aligns the language with how the JVM actually works.

Bonus Meme: Valhalla When?

Quoted from JEP 513 documentation:

JEP 401, from Project Valhalla, proposes value classes and builds upon this work. When the constructor of a value class does not contain an explicit constructor invocation then an implicit constructor invocation is considered to implicitly appear at the end of the constructor body, rather than the beginning. Thus all of the statements in such a constructor constitute its prologue, and its epilogue is empty.

Can we expect JEP 401 to enter first preview in JDK 26? Or will we be fighting against the Ragnarök itself before Project Valhalla is made a reality?

&quot;Valhalla When?&quot;

Sure, the meme doesn't quite fit, but honestly, Project Valhalla has become something of a legendary meme among Java developers by now.

Incompatibility Reports with JDK 25 Early Access

Some of the notable incompatibility problem I've encountered when testing JDK 25 Early Access:

Details
  • Lombok 1.18.38 does not work with JDK 25. Requires 1.18.40 or above.

  • Maven Surefire plugins requires this configuration for Mockito:

    <configuration>
    <argLine>
    -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar
    -Xshare:off
    </argLine>
    </configuration>