Programming & Java

New Features in Java 25: A Complete Guide with Code Examples (JDK 25 LTS)

Explore every major feature in Java 25 (JDK 25 LTS) with practical code examples — Compact Source Files, Scoped Values, Structured Concurrency, Stable Values, Flexible Constructors, Primitive Patterns, and performance improvements.

28 March 2026
18 min read
Java 25 LTS

Java 25, released on September 16, 2025, is the latest Long-Term Support (LTS) release from Oracle. It follows Java 21 as the next LTS milestone and brings a collection of features that simplify everyday coding, modernize concurrency, and deliver meaningful performance gains. Whether you are upgrading from Java 17 or Java 21, this guide walks through every major feature with practical code examples you can try today.

Compact Source Files and Instance Main Methods (JEP 512)

One of the most celebrated changes in Java 25 is the finalization of Compact Source Files. Previously, even a simple “Hello, World!” program required a class declaration, a public static void main(String[] args) signature, and often an import or two. JEP 512 removes that ceremony: you can now write an instance main method without an enclosing class, making Java friendlier for scripting, teaching, and quick prototyping.

The IO class for basic console I/O is now in java.lang and implicitly imported, so reading and writing to the console no longer needs System.out.println.

HelloWorld.java — Before Java 25
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
HelloWorld.java — Java 25 Compact Style
void main() {
    IO.println("Hello, World!");
}

The compiler wraps this in an implicit class automatically. You still have access to the full language — imports, methods, fields — but the entry point is dramatically simpler. This is particularly useful when teaching Java to beginners, who can focus on logic rather than syntax scaffolding.

Flexible Constructor Bodies (JEP 513)

Before Java 25, every statement in a constructor had to appear after the call to super() or this(). This made validation awkward: you had to pass unchecked values to the parent or extract logic into static helper methods. JEP 513 lifts that restriction, allowing statements to execute before the explicit constructor invocation.

Flexible Constructor — Java 25
public class PositiveRange extends Range {

    public PositiveRange(int low, int high) {
        // Validation BEFORE calling super — new in Java 25
        if (low < 0 || high < 0) {
            throw new IllegalArgumentException("Values must be non-negative");
        }
        if (low > high) {
            throw new IllegalArgumentException("low must be <= high");
        }
        super(low, high);
    }
}

This pattern keeps constructors readable and avoids the workaround of static factory methods or validation wrappers. The compiler still ensures that instance fields of the current class are not accessed before the superclass constructor completes.

Scoped Values (JEP 506) — Final

After five preview rounds, Scoped Values are now a permanent part of the platform. They offer a modern, safer, and more performant alternative to ThreadLocal for sharing immutable data across method calls within a thread — and they work especially well with virtual threads.

Unlike ThreadLocal, a scoped value is immutable within its bound scope and is automatically cleaned up when the scope ends. There is no risk of memory leaks from forgotten remove()calls.

ScopedValue Example
import java.lang.ScopedValue;

public class RequestHandler {

    private static final ScopedValue<String> CURRENT_USER =
        ScopedValue.newInstance();

    void handleRequest(String username) {
        ScopedValue.runWhere(CURRENT_USER, username, () -> {
            processOrder();
            sendNotification();
        });
    }

    void processOrder() {
        // Access the scoped value anywhere in the call chain
        String user = CURRENT_USER.get();
        System.out.println("Processing order for: " + user);
    }

    void sendNotification() {
        String user = CURRENT_USER.get();
        System.out.println("Sending notification to: " + user);
    }
}

When to use Scoped Values over ThreadLocal: prefer scoped values when the data is conceptually request-scoped (web request context, transaction ID, user identity) and should not be mutated by callees. They are cheaper to create and rebind, making them a natural fit for the high-concurrency model that virtual threads enable.

Structured Concurrency (JEP 505) — Fifth Preview

Structured Concurrency treats a group of related concurrent tasks as a single unit of work. If one subtask fails, all siblings are cancelled automatically — no more leaking threads or half-finished work. The API integrates tightly with virtual threads and scoped values.

Structured Concurrency Example
import java.util.concurrent.StructuredTaskScope;
import java.util.function.Supplier;

record UserProfile(String name, List<Order> orders) {}

UserProfile fetchUserProfile(long userId) throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

        Supplier<String>      name   = scope.fork(() -> fetchUserName(userId));
        Supplier<List<Order>> orders = scope.fork(() -> fetchOrders(userId));

        // Wait for both tasks; propagate the first failure
        scope.join().throwIfFailed();

        return new UserProfile(name.get(), orders.get());
    }
    // Both subtasks are guaranteed to be finished here
}

The ShutdownOnFailure policy cancels remaining subtasks the moment any one of them throws. An alternative policy, ShutdownOnSuccess, returns the first successful result and cancels the rest — useful for racing redundant requests against multiple backends.

Stable Values (JEP 502) — Preview

Stable Values provide a thread-safe, lazy initialization mechanism that the JVM can treat as a constant after first assignment. This eliminates the classic double-checked locking pattern and unlocks aggressive JIT optimizations.

StableValue Example
import java.lang.StableValue;

public class AppConfig {

    // Expensive connection — created only on first access
    private static final StableValue<DatabaseConnection> DB =
        StableValue.of();

    public static DatabaseConnection getConnection() {
        return DB.orElseSet(() -> {
            System.out.println("Creating database connection...");
            return new DatabaseConnection("jdbc:postgresql://localhost/mydb");
        });
    }
}

The first thread to call orElseSet runs the supplier; all subsequent calls return the cached value without locking. Because the JVM knows the value will never change after initialization, it can inline and constant-fold just like a static final field initialized at class-load time.

Stable collections are also provided. For example, StableValue.list()creates a lazily-populated list where each element is computed at most once:

Stable List Example
List<Logger> loggers = StableValue.list(10, i ->
    Logger.getLogger("module-" + i)
);

// Element 3 is created on first access; subsequent reads are instant
Logger log = loggers.get(3);

Primitive Types in Patterns (JEP 507) — Third Preview

Pattern matching now supports primitive types in instanceof and switch expressions. Previously, primitives required manual casting and range checks. JEP 507 lets you write cleaner, safer conversions by matching and extracting primitive values directly.

Primitive Patterns in switch
// Type-safe handling based on the actual value range
String describeHttpStatus(int status) {
    return switch (status) {
        case 200       -> "OK";
        case 301       -> "Moved Permanently";
        case 404       -> "Not Found";
        case int s when s >= 500 && s < 600 -> "Server Error: " + s;
        default        -> "Unknown: " + status;
    };
}
Primitive instanceof
// Safely narrow a long to int without manual range checking
Object value = fetchValue();

if (value instanceof int i) {
    System.out.println("int value: " + i);
} else if (value instanceof long l) {
    System.out.println("long value: " + l);
} else if (value instanceof double d) {
    System.out.println("double value: " + d);
}

The compiler performs exact and safely-narrowing conversions at the pattern match site, eliminating an entire class of subtle numeric-conversion bugs.

Module Import Declarations (JEP 511) — Preview

Tired of long import lists when using a library? Module Import Declarations let you import all public types exported by a module with a single statement. Your own code does not need to be inside a module to use this feature.

Module Import Declaration
// Instead of dozens of java.util.* imports:
import module java.base;

void main() {
    List<String> names = List.of("Alice", "Bob", "Charlie");
    Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 87);

    names.stream()
         .filter(n -> scores.getOrDefault(n, 0) > 90)
         .forEach(IO::println);
}

A single import module java.base covers collections, streams, I/O, concurrency, and more. This pairs well with Compact Source Files to make small scripts and teaching examples even cleaner.

Performance and Runtime Improvements

Java 25 includes several under-the-hood improvements that benefit applications without any code changes:

Compact Object Headers (JEP 519)

Object headers have been reduced from 96–128 bits to just 64 bits on 64-bit platforms. For applications that allocate millions of small objects — think microservices, data pipelines, or in-memory caches — this translates to significant memory savings and better cache utilization.

Generational Shenandoah (JEP 521)

The Shenandoah garbage collector now supports generational collection, matching ZGC in having both a single-generation and multi-generation mode. Generational mode focuses young-generation collections on short-lived objects, reducing GC overhead and improving throughput for most workloads.

JFR Enhancements (JEP 518, 520, 525)

Java Flight Recorder gains CPU-Time Profiling for more accurate hot-method identification, Cooperative Sampling that reduces profiling overhead in large applications, and Method Timing & Tracing for fine-grained latency analysis of specific methods without third-party agents.

Ahead-of-Time Compilation Improvements

AOT method profiling and streamlined command-line ergonomics make it easier to pre-compile hot paths. Combined with CDS (Class Data Sharing) improvements, application startup time continues to shrink release over release.

Key Derivation Function API (JEP 510)

A new standard API for key derivation functions (KDFs) such as HKDF joins the security provider framework. Applications that derive encryption keys from shared secrets or passwords can now use a portable, reviewed API rather than vendor-specific libraries.

Migration Tips: Moving to Java 25

Upgrading to Java 25 from earlier LTS versions (17 or 21) is straightforward for most projects, but a few areas deserve attention:

  • 32-bit x86 support removed (JEP 503) — if you still deploy to 32-bit Intel systems, you will need to migrate to a 64-bit OS or stay on an older JDK.

  • Preview features require opt-in — features like Stable Values (JEP 502), Primitive Patterns (JEP 507), and Module Imports (JEP 511) need --enable-preview at compile and run time.

  • Replace ThreadLocal with Scoped Values where the data is truly request-scoped and immutable. This is especially impactful if you are already using virtual threads.

  • Test your GC settings — if you were using Shenandoah, try the new generational mode. Benchmark before and after to confirm the improvement in your workload.

  • Update build tools — ensure Maven, Gradle, and your IDE support Java 25 source level. Most popular libraries (Spring, Quarkus, Micronaut) have Java 25 compatibility releases.

Conclusion

Java 25 is a landmark LTS release that balances developer experience with runtime performance. Compact Source Files and Flexible Constructors lower the bar for newcomers. Scoped Values and Structured Concurrency modernize multi-threaded code for the virtual-thread era. And under the hood, Compact Object Headers and Generational Shenandoah push throughput and memory efficiency forward.

If your team is evaluating the upgrade, the best approach is to start with a non-production service, enable preview features in a test environment, and measure real-world metrics. The combination of cleaner syntax, safer concurrency, and free performance gains makes Java 25 a compelling target for any project that values long-term stability.

Java 25 Feature Summary

FeatureJEPStatus
Compact Source Files512Final
Flexible Constructor Bodies513Final
Scoped Values506Final
Structured Concurrency505Fifth Preview
Stable Values502Preview
Primitive Types in Patterns507Third Preview
Module Import Declarations511Preview
Compact Object Headers519Final
Generational Shenandoah521Final
Key Derivation Function API510Final
Vector API508Tenth Incubator

Java 25 proves that the platform continues to evolve in meaningful ways — making code simpler to write, safer to run concurrently, and faster to execute. With LTS backing, it is the release to bet on for production systems in 2025 and beyond.


?

Frequently asked questions

Java 25 (JDK 25) reached General Availability on September 16, 2025. It is a Long-Term Support (LTS) release, meaning it receives extended security patches and bug fixes — making it suitable for production deployments.

The headline features include Compact Source Files (JEP 512) for simpler entry points, Scoped Values (JEP 506) as a modern ThreadLocal replacement, Structured Concurrency (JEP 505) for safer multi-threaded code, Stable Values (JEP 502) for lazy initialization, Flexible Constructor Bodies (JEP 513), and Compact Object Headers (JEP 519) for memory savings.

No. Scoped Values (JEP 506) are finalized in Java 25 and do not require the --enable-preview flag. However, features like Stable Values (JEP 502), Primitive Patterns (JEP 507), and Module Import Declarations (JEP 511) are still in preview and require --enable-preview.

Structured Concurrency (StructuredTaskScope) treats a group of concurrent tasks as a single unit. If one subtask fails, the scope automatically cancels remaining subtasks. This prevents thread leaks and partial failures that are common with unstructured ExecutorService usage, especially when using virtual threads.

Yes, for most projects the upgrade is worthwhile. Java 25 brings finalized features (Scoped Values, Compact Source Files, Flexible Constructors), significant performance improvements (Compact Object Headers, Generational Shenandoah), and enhanced security APIs. Since both are LTS releases, migrating gives you a longer support window.

Back to all articlesPublished · FreeToolSuite Blog

FreeToolSuite — 100% free online tools, no sign-up required.

© 2026 FreeToolSuite. MIT.

We use cookies and Google Analytics to understand how visitors use our site so we can improve your experience. No personal data is collected. You can accept or decline analytics cookies.