Martin Ahrer

Thinking outside the box

Java 25 major language and API improvements

2025-09-30 8 min read Martin

Java ㉕ has been released in September 2025.

The following content is summarizing the most important changes to the Java API, the Java language, and the JVM introduced with Java Java ㉕.

For a more complete overview, follow the links in the following sections referring to the official Oracle release documents.

Java 25 Language improvements

The following describes selected improvements of the Java language. See the Java 25 release notes. See Java Language Updates for Java SE 25.

Java 25 is introducing the following finalized language enhancements.

  • JEP 511 is finalizing Module Import Declarations. Enhance the Java programming language with the ability to succinctly import all of the packages exported by a module. This simplifies the reuse of modular libraries, but does not require the importing code to be in a module itself.

  • JEP 512 is finalizing Compact Source Files and Instance Main Methods. Enhance the Java programming language so that students can write their first programs without needing to understand language features designed for large programs.

  • JEP 513 is finalizing Flexible Constructor Bodies. In constructors in the Java programming language, allow statements that do not reference the instance being created to appear before an explicit constructor invocation.

Java 25 is introducing the following language enhancements as developer preview.

  • JEP 507 is continuing with Primitive Types in Patterns, instanceof, and switch (Third Preview). Enhance pattern matching by allowing primitive type patterns in all pattern contexts, and extend instanceof and switch to work with all primitive types.

We are not diving into the details of these preview features and defer that until they are moving from preview for general availability.

Instance Main Methods

Java 25 also allows instance main methods within classes, eliminating the need for static methods and making object-oriented concepts more natural for beginners.

Static Main Before Java25
class Application {
  public static void main(String[] args) {
  }
}
Application.main(new String[]{});
Instance Main Method
class Application {
  void main() {
    // Can directly call instance methods
    displayWelcome();
    processData();
  }
  
  void displayWelcome() {
    System.out.println("Welcome to Java 25!");
  }
  
  void processData() {
    System.out.println("Processing data with instance methods");
  }
}

Application app = new Application();
app.main();

With Java 25, main methods can be instance methods, making the transition to object-oriented programming more natural and intuitive.

Object Oriented Approach
class Calculator {
  void main() {
    var result=add(0, 10);
    result= multiply(result, 3);
    System.out.println("Result: " + result);
  }
  int add(int left, int right) {
    return left + right;
  }
  int multiply(int left, int right) {
    return left * right;
  }
}

Calculator calc = new Calculator();
calc.main();

Instance main methods make it easier to teach object-oriented concepts since students work with objects from the beginning.

Module Import Declarations

Module Import With Java25
import module java.base;
    List<String> list = List.of("a", "b", "c");
    Map<String, Integer> map = Map.of("key", 1);
    Set<String> set = Set.of("x", "y", "z");

Java 25 introduces module import declarations that allow importing all exported packages from a module with a single statement using the import module syntax.

Flexible Constructor Bodies

Before Java 25, constructor bodies were restricted - all statements had to appear after the explicit constructor invocation (super() or this()).

Constructor Before Java25
class BeforeJava25 {
  private final String name;
  private final int value;
  
  public BeforeJava25(String name, int value) {
    this.name = validateName(name);
    this.value = Math.max(0, value);
  }
  
  private String validateName(String name) {
    return name != null ? name.trim() : "default";
  }
}

BeforeJava25 obj = new BeforeJava25("test", -5);
Constructor With Java25
class WithJava25 {
  private final String name;
  private final int value;
  
  public WithJava25(String name, int value) {
    String validatedName = name != null ? name.trim() : "default";
    int validatedValue = Math.max(0, value);
    this.name = validatedName;
    this.value = validatedValue;
  }
}

var obj = new WithJava25("  test  ", -10);

Java 25 allows statements that don’t reference the instance being created to appear before the explicit constructor invocation, providing more flexibility.

Java 25 API improvements

The following describes selected improvements of the Java API. See the Java 25 release notes.

Java 25 is introducing the following finalized API enhancements.

  • JEP 506 is finalizing Scoped Values. Enable the sharing of immutable data within and across threads. They are preferred to thread-local variables, especially when using large numbers of virtual threads.

  • JEP 510 is introducing Key Derivation Function API. Provide an API for Key Derivation Functions (KDFs), which are cryptographic algorithms for deriving additional keys from a secret key or other high-entropy key material.

Code samples are only available for selected JEPs!

Java 25 is continuing the following API enhancements as developer preview or incubator.

  • JEP 470 is starting with PEM Encodings of Cryptographic Objects (Preview). Provide standard APIs for encoding and decoding cryptographic objects in PEM format, enabling interoperability with other systems and tools.

  • JEP 502 is starting with Stable Values (Preview). Introduce stable values, which are immutable objects that can be shared safely across threads and provide better performance characteristics than traditional immutable objects.

  • JEP 505 is continuing with Structured Concurrency (Fifth Preview). Simplify multithreaded programming by introducing an API for structured concurrency. Structured concurrency treats multiple tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability.

  • JEP 508 is continuing with Vector API (Tenth Incubator). Introduce an API to express vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures, thus achieving performance superior to equivalent scalar computations.

We are not diving into the details of these preview features and defer that until they are moving from preview for general availability.

Scoped Values

Before Java 25, ThreadLocal variables were used to share request context across method calls. ThreadLocal requires manual cleanup and has memory overhead with virtual threads.

Request Context With Thread Local
final ThreadLocal<String> REQUEST_ID_THREAD_LOCAL = new ThreadLocal<>();
REQUEST_ID_THREAD_LOCAL.set("REQ-12345");
try {
  processRequest();
  String requestId = REQUEST_ID_THREAD_LOCAL.get();
  assertEquals("REQ-12345", requestId);
} finally {
  // Must manually clean up to prevent memory leaks
  REQUEST_ID_THREAD_LOCAL.remove();
}
Request Context With Scoped Value
final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
ScopedValue.where(REQUEST_ID, "REQ-12345")
    .run(() -> {
      processRequest();
      String requestId = REQUEST_ID.get();
      assertEquals("REQ-12345", requestId);
    });
// Automatically cleaned up after scope
assertFalse(REQUEST_ID.isBound());

Java 25 Scoped Values provide immutable request context sharing with automatic cleanup. No manual cleanup needed - values are automatically removed after scope ends.

Scoped Values automatically propagate request context to child virtual threads without the memory overhead that ThreadLocal would incur with thousands of threads.

Request Context Across Virtual Threads
final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
ScopedValue.where(REQUEST_ID, "REQ-12345").run(() -> {
  try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 5; i++) {
      final int taskId = i;
      executor.submit(() -> {
        // Each virtual thread inherits the request context
        String requestId = REQUEST_ID.get();
        System.out.println("Task " + taskId + " processing: " + requestId);
        assertEquals("REQ-12345", requestId);
        return null;
      });
    }
    executor.shutdown();
    assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
  } catch (InterruptedException e) {
    Thread.currentThread().interrupt();
  }
});
Nested Request Context
final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
ScopedValue.where(REQUEST_ID, "REQ-12345").run(() -> {
  assertEquals("REQ-12345", REQUEST_ID.get());

  // Rebind for sub-request
  ScopedValue.where(REQUEST_ID, "REQ-67890").run(() -> {
    assertEquals("REQ-67890", REQUEST_ID.get());
    System.out.println("Sub-request: " + REQUEST_ID.get());
  });

  // Original request context restored
  assertEquals("REQ-12345", REQUEST_ID.get());
});

Request context can be nested and rebound for sub-operations. Inner scopes shadow outer values, but outer values are restored when inner scope completes.

Request Context With Return Value
final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
String result = ScopedValue.where(REQUEST_ID, "REQ-12345")
    .call(() -> {
      String requestId = REQUEST_ID.get();
      return "Processed request: " + requestId;
    });

assertEquals("Processed request: REQ-12345", result);

Request context can be bound when computing and returning values using call instead of run. This is useful when operations need to return results while maintaining context.

Multiple Request Context Values
final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
ScopedValue<String> USER_ID = ScopedValue.newInstance();
ScopedValue<String> TRACE_ID = ScopedValue.newInstance();

ScopedValue.where(REQUEST_ID, "REQ-12345")
    .where(USER_ID, "user-789")
    .where(TRACE_ID, "trace-abc")
    .run(() -> {
      assertEquals("REQ-12345", REQUEST_ID.get());
      assertEquals("user-789", USER_ID.get());
      assertEquals("trace-abc", TRACE_ID.get());

      System.out.println("Request: " + REQUEST_ID.get() +
          ", User: " + USER_ID.get() +
          ", Trace: " + TRACE_ID.get());
    });

Multiple request context values can be bound simultaneously for richer context. This is useful when tracking request ID, user ID, and trace ID together.

Java 25 JVM improvements

The following describes selected improvements of the Java Virtual Machine. See the Java 25 release notes.

Java 25 is introducing the following finalized JVM enhancements.

  • JEP 518 is introducing JFR Cooperative Sampling. Enhance Java Flight Recorder (JFR) with cooperative sampling capabilities, allowing applications to participate in profiling decisions and reduce overhead.

  • JEP 519 is introducing Compact Object Headers. Reduce the memory footprint of Java objects by implementing compact object headers, improving memory efficiency and performance.

  • JEP 520 is introducing JFR Method Timing & Tracing. Enhance Java Flight Recorder with improved method-level timing and tracing capabilities for better application performance analysis.

  • JEP 521 is introducing Generational Shenandoah. Improve application performance by extending Shenandoah to maintain separate generations for young and old objects, allowing more frequent collection of short-lived objects.

Java 25 is introducing the following experimental JVM enhancements.

  • JEP 509 is starting with JFR CPU-Time Profiling (Experimental). Enhance Java Flight Recorder with experimental CPU-time profiling capabilities to provide more detailed performance insights.

Java 25 Tooling and Platform improvements

The following describes selected improvements of the Java platform and tooling. See the Java 25 release notes.

Java 25 is introducing the following platform and tooling enhancements.

  • JEP 503 is Remove the 32-bit x86 Port. Remove the 32-bit x86 port (linux-x86) from the JDK, continuing the modernization of supported platforms.

  • JEP 514 is introducing Ahead-of-Time Command-Line Ergonomics. Improve the usability of ahead-of-time compilation by enhancing command-line tools and their ergonomics for better developer experience.

  • JEP 515 is introducing Ahead-of-Time Method Profiling. Enable method-level profiling information to be collected and used during ahead-of-time compilation to improve application startup and performance.