Skip to content

SimonVerhoeven/java25-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Java 25

Java 25 will be released on the 16th of September 2025, and once again it helps move the language forwards. An important thing to keep in mind for organizations is that this is a Long Term Support (LTS) release.

The linked repository will include code samples, do keep in mind that you will need to enable the preview mode of your JDK for those marked as such, and the same goes for experimental ones.

Note
Teaser of enhancements
  • Faster Startup: Simplified AOT caching and profiling (JEP 514, 515).

  • Lower memory consumption: Production-ready Compact Object Headers (JEP 519).

  • Better GC: Generational Shenandoah now production (JEP 521).

  • Easier Debugging: Precise JFR method tracing (JEP 520).

  • Modernization: Key previews for concurrency (Scoped Values, Structured Concurrency) and security (PEM API).

Why Upgrade?

Java 25 LTS offers enterprises:

  • Proven memory efficiency gains

  • Reliable garbage collection improvements

  • Faster application startup

  • Enhanced production monitoring

  • Long-term support stability

This release solidifies Java’s position as the premier platform for demanding enterprise workloads.

Core

This preview API introduces immutable value holders that are at most initialized once as it will help us move towards deferred immutability through StableValue and StableSupplier.

One of the key benefits of stable values is in how they’re handled by the JVM. Internally the stable values use the internal @Stable annotation which despite the values being non-final marks them as not changing after the initial updates. Thanks to this, these values benefit from the same constant-folding optimizations as final-fields.

This proposal aims to address some of the frequently encountered challenges in concurrent programming by introducing an API that treats groups of related tasks running in different threads as a single unit of work.

The proposed approach, also known as structured concurrency, will help us streamline error handling, cancellation, and observability, making concurrent code more reliable and easier to manage.

The main target is promoting a style of concurrent programming that eliminates common risks such as thread leaks and cancellation delays, while also improving the observability of concurrent code. The API itself is centered around StructuredTaskScope, which allows us to define a clear hierarchy of tasks and subtasks, ensuring that all subtasks are completed or cancelled before the parent task exits. This enforces a structured flow of execution, similar to single-threaded code, where subtasks are confined to the lexical scope of their parent task. The API also provides built-in shutdown policies (e.g., ShutdownOnFailure and ShutdownOnSuccess) to handle common concurrency patterns, such as cancelling all subtasks if one fails or succeeds. We can also define our own shutdown policies.

By reifying the task-subtask relationship at runtime, structured concurrency makes it easier for us to reason about and debug concurrent programs. It also integrates well with observability tools, such as thread dumps, which can now display the hierarchical structure of tasks and subtasks.

This enhancement does not aim to replace existing concurrency constructs like ExecutorService or Future, but rather to complement them by offering a more structured and reliable alternative for managing concurrent tasks.

Scoped values are being introduced as a preview API for the fourth time. It will enable methods to share immutable data both within threads and with child threads, offering better efficiency and clarity than thread-local variables, especially when used with virtual threads and structured concurrency.

The fourth iteration mostly remains the same, the only change is the removal of callWhere() and runWhere() from ScopedValue thus leaving us with a fluent API. After this change we can only use bound scopes values through the call() and run() methods from ScopedValue.Carrier.

The tenth iteration of an API for vector computations. It aims to be a way to express these in a manner that compiles reliably to optimal vector instructions on supported CPU architectures to achieve better performance than equivalent scalar computations.

Compared to JEP 489: Vector API (Ninth Incubator) there are no API changes.

Two important changes are the increased usage of auto-vectorization on supporting x64 CPUs and now leveraging the foreign function and memory API to link native mathematical-function libraries.

The developers have confirmed that the API will remain in incubation until project Valhalla enters preview itself so that the API can leverage the expected performance and in-memory representation enhancements.

HotSpot

As planned after the deprecation for removal in JDK 24 (JEP 501) the 32 bit X86 bit source code and build support has been removed.

This experimental feature enhances the JDK Flight Recorder with the capacity to capture CPU-time profiling information on Linux.

This enhancement simplifies the creation of ahead-of-time (AOT) caches to speed up Java application startup by introducing a new -XX:AOTCacheOutput option, which consolidates the previous two-step process (recording a training run and then generating the cache) into a single command. The JVM now automatically handles the intermediate AOT configuration file as a temporary file, removing cleanup overhead, while still supporting explicit two-step workflows for advanced use cases like distributed training and cache generation. A new JDK_AOT_VM_OPTIONS environment variable allows separate tuning for cache creation without affecting the training run, maintaining flexibility for future AOT optimizations under Project Leyden. The change prioritizes usability for common scenarios without removing the ability to manually control the process when needed, such as when optimizing cache generation across different hardware environments.

This would facilitate improved application warmup times by storing method-execution profiles from training runs in the AOT cache, allowing the JIT compiler to optimize critical methods immediately at startup instead of waiting for runtime profiling.

This approach leverages existing AOT workflows (introduced in JEP 483) to cache method behavior data—such as call frequencies and type information—without modifying application code. This enables faster peak performance in production while still allowing adaptive optimizations if runtime behavior diverges. For example, a stream-heavy demo saw a 19% speedup with cached profiles, as the JIT could prioritize hot methods earlier.

While ideal for mixed AOT/JIT scenarios, future work may expand this with full AOT compilation, and risks remain minimal since the feature builds on the established AOT cache mechanism.

JEP 518 enhances the JDK Flight Recorder (JFR) by introducing cooperative sampling to improve stability and reduce savepoint bias. Instead of using unsafe heuristics to sample thread stacks asynchronously, the new approach suspends threads, records program counters, and processes stack traces at safe points via a thread-local queue, minimizing crashes and overhead. This redesign simplifies stack-trace generation, improves scalability, and maintains profiling accuracy, though some bias remains in intrinsic methods and native code, which will be addressed in future updates.

This feature is being promoted from experimental to a product feature.

This alternative object-header layout which was introduced through JEP 450: Compact Object Headers (experimental) has proven its value and has been thoroughly vetted by Oracle and Amazon. Furthermore, the SPECjbb2015 benchmark showed significant gains.

To enable this feature we only need to add the -XX:+UseCompactObjectHeaders command-line option, -XX:+UnlockExperimentalVMOptions is no longer needed.

JEP 520 enhances the JDK Flight Recorder (JFR) with targeted method timing and tracing via bytecode instrumentation, enabling precise profiling of specific methods without source modifications.

We can instrument methods, classes, or annotations (such as java.util.HashMap::resize or @jakarta.ws.rs.GET) through filters via command-line, config files, or JMX.

java -XX:StartFlightRecording:jdk.MethodTrace#filter=java.util.HashMap::resize,filename=java25demo.jfr ...
$ jfr print --events jdk.MethodTrace --stack-depth 20 java25demo.jfr

The above snippet would trace what triggered HashMap resizing.

While it is designed for low overhead, the feature explicitly warns against instrumenting large numbers of methods simultaneously, as this could degrade performance—recommending sampling. We should use method sampling instead in such cases.

Two new events are introduced: jdk.MethodTiming (aggregate invocations/durations) and jdk.MethodTrace (per-call stacks).

Future work may expand filtering (by interface for example), but logging method arguments/fields is excluded for security.

This proposal will promote the generational mode of the Shenandoah garbage collector from experimental to a production feature. The single-generation mode will remain the default for now.

Given that the Security Manager is no longer used in JFR a public constructor and a record class has been added for an event configuration object. This change helps avoid reflection and slightly reduces overhead.

This new command-line flag will ensure that events are output in a human-readable format, while numbers, durations, and timestamps will be output with full precision. This flag will enhance reporting without the noise from --json.

Language specification

This JEP, first introduced as JEP-455, returns without any changes. It aims to enhance pattern matching by allowing primitives in all pattern contexts and allowing one to use them with instanceof and switch as well.

if (someObject instanceof int someInt) {
    System.out.println("The int was: " + someInt);
}

This will allow us to easily import all packages exported by a module, this facilitates the reuse of modular libraries without requiring the importing code to be within a module itself. It will also allow beginners to more easily use third-party libraries and core Java classes without needing to know their exact location within the package hierarchy.

For example: import module java.base;.

If you’re frequently using star imports, you can swap to module imports. Do try to avoid overusing it.

We do need to pay attention to ambiguous reference. To clarify, let’s use the following code:

import module java.base;      // includes java.util.Date
import module java.sql;       // includes java.sql.Date

public class Main {
    static void main() {
        Date releaseDate = Date.valueOf("2025-09-16");
        System.out.println("Java 25 released on: " + releaseDate);
    }
}

The above code will lead to an error that Date is an ambiguous reference given that it could refer to both java.util.Date and java.sql.Date.

To resolve this we’ll need to add an explicit import.

This enhancement will enable simplified programs by allowing them to be defined in an implicit class and an instance method void main().

The following is now a valid declaration:

void main() {
    System.out.println("This is possible since Java 25!");
}

I’ve really been enjoying this feature while scripting, and it will be a nice aid when teaching.

This Java language feature allows statements before explicit constructor invocations, enabling more natural field initialization.

The enhancement introduces two constructor phases: a prologue and epilogue respectively to help developers place initialization logic more intuitively while preserving existing instantiation safeguards.

Argument validation, or shared setup is thus now cleanly possible in one place which enhances the readability and allows us to fail-fast.

For example:

 public Super(int meaningOfLife) {
    if (MEANING_OF_LIFE != meaningOfLife) {
        throw new IllegalArgumentException("Please read Hitchhiker's guide to the galaxy");
    }
    super(meaningOfLife);
}

Security

JEP 470 introduces a preview API in Java 25 for encoding/decoding cryptographic objects (keys, certificates, CRLs) to/from the PEM (Privacy-Enhanced Mail) format, simplifying a previously manual and error-prone process.

The API centers on DEREncodable, PEMEncoder, and PEMDecoder classes, supporting standards like PKCS#8 and X.509, with built-in encryption for private keys.

The goals include ease of use and interoperability with tools like OpenSSL thus addressing a gap highlighted by developer surveys.

The design avoids extending legacy APIs such as KeyFactory in favour of a dedicated, immutable, and thread-safe solution, though encrypted keys require password handling via withEncryption()/withDecryption().

It is possible that future iterations may expand support for non-standard PEM types via PEMRecord.

As this is a preview feature, you’ll need to enable it through --enable-preview to experiment with it.

This enhancement introduces an API to derive additional keys from a secret key and other data through cryptographic algorithms as Key Derivation Functions (KDFs).

KDF is part of the cryptographic standard PKCS #11 and is one of the key elements needed to implement Hybrid Public Key Encryption (HPKE). HPKE is a post-quantum cryptographic algorithm designed to be resistant to quantum computers.

Thoughts

Java 25 delivers significant improvements, and the future holds even more promise. Java 26 is expected to advance major projects like Valhalla (value objects) and Leyden (static compilation), which aim to revolutionize performance and startup times.

With each release, Java evolves, delivering better efficiency, simpler syntax, and stronger security, all while prioritizing developer onboarding. Upgrading now ensures readiness to leverage these innovations as they arrive, keeping applications fast, modern, and maintainable.

Remember, the Long-Term Support (LTS) label is mostly relevant for organizations requiring vendor support over many years, be it for actual support or regulatory reasons. For other projects, the regular six-month release cycle offers us a low-risk way to continuously integrate improvements and provide feedback on the evolution of the language.

In this article I’ve touched upon the major enhancement proposals, but that certainly doesn’t mean there haven’t been any other improvements. For example, we can now read all remaining characters from a Reader into a String by using the new method readAllAsString() which as delivered through JDK-8354724 or the option to construct ZIP FileSystem as read-only JDK-8350880 so as always I strongly recommend checking out the release notes and experimenting yourself.

Migrating

While most changes in Java 25 are backward-compatible, teams should note:

  • Deprecations: The removal of 32-bit x86 support (JEP 503) may require updates for legacy deployments.

  • Preview Features: APIs like StableValue and PEM encoding require --enable-preview flags and may evolve further. If you were running 22 and experimenting with StringTemplates, that preview was removed.

  • Tooling: Ensure build tools (Maven/Gradle), CI pipelines, Lombok, and monitoring agents (e.g., APM tools) are tested with Java 25’s new JFR events and AOT workflows.

For large codebases, incremental adoption via jdeprscan and jlink can help isolate compatibility risks.

Resources

Some useful resources to dive deeper into the Java ecosystem and stay up to date are:

  • GitHub repository - this article’s code

  • The release notes - The official source for all changes, including new features, bug fixes, and deprecations

  • The Java version almanac - A great resource with details on distributions, and API differences between various releases

  • Foojay - A magnificent Java community offering articles, tutorials, and discussions on the latest in the Java ecosystem

  • SDKman! - a great tool to manage the installation of various tools and languages

  • Inside Java - News updates by Java team members at Oracle

  • Java Community Process - the place where people can propose, discuss, and approve new features through a Java Specification Request (JSR)

  • How to Upgrade to Java 25 #RoadTo25

About

No description, website, or topics provided.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages