Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Java 25 in Practice

Java 25 in Practice

Practical Examples of JDK 25 Features for Cleaner, Safer, and Faster Java Development

Eugene Obiedkov

by Eugene Obiedkov

Full Stack Developer

Feb, 2026
13 min read

facebooklinkedintwitter
copy
Java 25 in Practice

The release of JDK 25 marks a significant LTS (Long-Term Support) version that brings not only internal optimizations but also noticeable changes to the language itself. We can finally write shorter programs without syntactic clutter, manage data across threads without headaches, and work with memory more efficiently.

Instead of lengthy JEP descriptions, we'll dive into specific code fragments so you can immediately see how the new features simplify everyday development. Let's get started!

Writing Code Faster

The most significant change for beginners or those writing small utilities is the radical simplification of the program entry point.

The Minimal Program

Previously, to print "Hello, World!", you needed to remember modifiers like public, static, void, and the String array. While not terribly complex for experienced developers, it created unnecessary noise.

How it was (before JDK 25):

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

How it is now (JDK 25):

void main() {
    IO.println("Hello, World!");
}

The compiler now "fills in" the missing parts automatically. It understands that if the main method isn't static, it should be called on an instance of an implicitly created class. The IO class is a new convenient way to handle input/output for simple scripts. The code has become concise and reads almost like a scripting language while remaining strictly typed Java.

Validation in Constructors

The second important change concerns constructors. Previously, you couldn't write any logic before calling super() or this(). This created problems when you needed to validate arguments before passing them further.

The Problem (before JDK 25):

public class PositiveNumber {
    private final int value;

    public PositiveNumber(int value) {
        if (value <= 0) {
            throw new IllegalArgumentException("Number must be positive");
        }
        this.value = value;
    }
}

The Solution (JDK 25):

public class NamedPositiveNumber extends PositiveNumber {
    private final String name;

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

The constructor execution sequence is now intuitive: first data preparation (validation, transformation), then the parent constructor call with ready parameters, and only then initialization of your own fields. This makes code safer and eliminates the need for static factory methods just for validation.

Run Code from Your Browser - No Installation Required

Run Code from Your Browser - No Installation Required

Less Memory: Compact Object Headers

This change doesn't require writing new code, but it directly affects how much memory your application consumes. Let's see how it works with an example.

// Class for storing pixel coordinates
public class Pixel {
    private int x;
    private int y;

    public Pixel(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

In memory, this object consists of two parts:

  • Header: Metadata (hash code, lock information, class reference)
  • Fields: The actual data (x and y, 4 bytes each)

What changed:
On 64-bit systems, the object header in JDK 24 and earlier occupied 96-128 bits (12-16 bytes). In JDK 25 (JEP 519), it occupies exactly 64 bits (8 bytes).

Before JDK 25: A Pixel object occupied ~16 (header) + 8 (fields) = 24 bytes
In JDK 25: A Pixel object occupies 8 (header) + 8 (fields) = 16 bytes

That's a 33% saving! For an image cache with 10 million pixels, your application saves ~80 MB of RAM automatically.

Threads Without Pain

Working with ThreadLocal has always been dangerous: it's easy to forget to clear data after use, leading to memory leaks, especially in applications with thread pools. JDK 25 introduces ScopedValue, a safer approach for immutable contextual data.

The Problem with ThreadLocal:

public class UserContext {
    private static final ThreadLocal<String> currentUser = new ThreadLocal<>();

    public static void setUser(String user) {
        currentUser.set(user);
    }

    public static String getUser() {
        return currentUser.get();
    }

    public static void clear() {
        currentUser.remove();
    }
}

Usage with potential memory leak:

public void handleRequest(String userId) {
    UserContext.setUser(userId);
    try {
        process();
    } finally {
        UserContext.clear();
    }
}

ScopedValue solution (JDK 25):

public class UserContext {
    public static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
}

public void handleRequest(String userId) {
    ScopedValue.where(UserContext.CURRENT_USER, userId)
               .run(() -> {
                   process(); 
                   anotherProcess();
               });
}

public void process() {
    String user = UserContext.CURRENT_USER.get();
    System.out.println("Processing for user: " + user);
}

Key difference: ScopedValue is immutable and bound to a scope, not a thread. After leaving the run() block, the value becomes inaccessible, eliminating memory leaks and ensuring thread safety.

Modern Cryptography

Before JDK 25, deriving a secure encryption key from a password required external libraries like Bouncy Castle. Now it's built-in.

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.KeySpec;
import java.util.HexFormat;

public class PasswordBasedEncryption {

    public static void main(String[] args) throws Exception {
        String password = "MySecretPassword";
        byte[] salt = "randomSalt123".getBytes();

        KeySpec spec = new Argon2KeySpec(password.toCharArray(), salt, 65536, 10, 1, 256);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("Argon2id");
        SecretKey key = factory.generateSecret(spec);
        SecretKeySpec aesKey = new SecretKeySpec(key.getEncoded(), "AES");

        System.out.println("AES-256 key successfully created from password!");
        System.out.println("Key bytes: " + HexFormat.of().formatHex(aesKey.getEncoded()));
    }
}

The Argon2id parameters (64MB memory, 10 iterations) transform a weak password into a strong 256-bit key. Previously, a multi-megabyte library was required. Now, JDK 25 handles it natively, simplifying builds and improving security.

Start Learning Coding today and boost your Career Potential

Start Learning Coding today and boost your Career Potential

Managing Task Groups

Manual thread management is error-prone. JDK 25 (preview) introduces structured concurrency to simplify parallel tasks.

import java.util.concurrent.*;

public class UserFinder {

    record User(String name, String email) {}

    public User findUser(String id) throws Exception {
        try (var scope = new StructuredTaskScope.ShutdownOnSuccess<User>()) {
            Subtask cached = scope.fork(() -> searchInCache(id));
            Subtask database = scope.fork(() -> searchInDatabase(id));
            scope.join();
            return scope.result();
        }
    }

    private User searchInCache(String id) throws InterruptedException {
        Thread.sleep(10);
        throw new RuntimeException("Not found in cache");
    }

    private User searchInDatabase(String id) throws InterruptedException {
        Thread.sleep(100);
        return new User("John", "john@mail.com");
    }

    public static void main(String[] args) throws Exception {
        var finder = new UserFinder();
        User user = finder.findUser("123");
        System.out.println("Found: " + user);
    }
}

Key points:

  • Automatic cancellation prevents thread leaks;
  • Lifecycle boundaries are clear and bounded;
  • Error handling is predictable.

Structured concurrency makes asynchronous code as simple and reliable as synchronous code.

Summary

JDK 25 makes code shorter, clearer, and safer. With concise main syntax, intelligent memory management, ScopedValue, built-in key derivation, and structured concurrency, developers can focus on business logic instead of boilerplate. These examples show the direction modern Java is heading.

FAQ

Q: Is JDK 25 free to use?
A: Yes. JDK 25 is provided under open-source licenses (like GPL with Classpath Exception), meaning you can download, use, and distribute it without paying licensing fees. Some vendors may offer optional support or enterprise builds for a fee, but the core JDK 25 is free.

Q: Is it safe to use JDK 25 in production?
A: Generally, yes. JDK 25 is a Long-Term Support (LTS) release, meaning it receives security patches and updates for several years. Always download from official sources like Oracle or OpenJDK distributions and keep it updated.

Q: Can I run older Java programs on JDK 25?
A: Mostly yes. JDK 25 is backward compatible, so most code written for previous versions will run without modification. However, some deprecated features may be removed, so testing your applications is recommended.

Q: Do I need to change my existing code to use new features?
A: No, your existing code will continue to work. To take advantage of simplified main methods, constructor validation, ScopedValue, compact object headers, or structured concurrency, you need to opt-in by rewriting parts of your code using these new features.

Q: Are the new memory and threading improvements automatic?
A: Yes. Compact object headers reduce memory usage automatically, and ScopedValue provides safer thread-local-like behavior when you choose to use it. Structured concurrency requires using the new APIs, but memory efficiency improvements work behind the scenes.

Var denne artikkelen nyttig?

Del:

facebooklinkedintwitter
copy

Var denne artikkelen nyttig?

Del:

facebooklinkedintwitter
copy

Innholdet i denne artikkelen

Vi beklager at noe gikk galt. Hva skjedde?
some-alt