The Java programming language has been part of the software world for close to thirty years. It has made its name in enterprise systems, Android apps, backend services, and large-scale infrastructure. Over time, the language has evolved significantly, with each new version bringing updates that aim to balance new ideas with long-term stability.
New Java versions come out every six months now, but most of them are short-lived. The ones marked for long-term support come out every couple of years, give or take. Right now, Java 21 holds that spot. That won’t be true for much longer.
Java 25 is almost here. It’s not trying to reinvent anything, but there’s enough happening under the surface to pay attention. Some of the changes have been in the works for years. Others are small, quiet updates that could still shift how things run in production.
If you’ve stuck with Java 17 or 21, this release might be the one that nudges you forward. There’s movement on long-promised features, cleaner handling of memory, and fewer leftovers from older versions. Some parts are still in preview. Others are final now, ready to use if your stack allows it.
This post gives a plain overview of what’s coming in Java 25. Nothing too deep, just enough to see what matters and what might affect your work.
Java 25 is set to land in September 2025. Unless something unusual happens between now and then, the release window will stay as planned. Oracle has stuck to the six-month cycle for a while now.
What makes this release stand out is that it’s an LTS version. That tag carries more weight than the date itself. Teams that don’t upgrade often tend to wait for these. Java 21 has filled that role since 2023, but it’s already nearing the point where people start looking ahead. Once Java 25 arrives, the shift will begin, slowly at first, then faster as support deadlines and upgrade plans start catching up.
Java 25 brings a mix of updates across the language, libraries, and runtime. Some changes are final, some are still in preview, and a few remain in early incubation. Here's a breakdown of what's being introduced or refined in this release. We’ll break down these features in four categories: previews, incubators, experimental, and final features.
This feature adds a simple way to encode and decode cryptographic data in PEM format. Java didn’t have a built-in way to do this before, so developers often had to use third-party libraries or write manual conversion code.
With this addition, you can work directly with PEM-encoded keys and certificates. It supports standard formats like PKCS8 and X509, which makes it easier to integrate with other systems that already use PEM, including many based on OpenSSL.
Stable values are objects that behave like constants but allow more control over when and how they are initialized. They are always immutable and are created once per runtime. This can help reduce startup time for large applications where too much happens all at once during initialization.
The JVM treats stable values in a way that allows performance optimizations, similar to what it does for final fields. They are useful when you want consistency without giving up flexibility in the startup sequence.
Java 25 brings real changes to how developers write and structure code. To get the most out of it, you need people who know how to work with both the new features and the old foundations. Hire Java developers who can help you modernize safely, avoid tech debt, and build software that’s easier to maintain going forward.
Structured concurrency changes how concurrent code is written and managed. It treats a group of tasks that run together as a single unit. If one task fails or gets canceled, the others are handled the same way. This helps prevent stray threads from continuing after they’re no longer needed.
The goal here is to make concurrent programming easier to reason about. Instead of juggling thread lifecycles manually, developers can focus on the structure of the work itself. In this update, the way StructuredTaskScope is created has changed. It now uses factory methods instead of public constructors.
This update makes it possible to use primitive types like int and double in pattern matching. It applies to both the instanceof keyword and switch expressions. Earlier versions only supported reference types, which limited how developers could use patterns in certain cases. With this change, you can write simpler and safer conditions without casting values or wrapping primitives in objects.
The goal is to make pattern matching feel more complete. It reduces edge cases and opens the way for more uniform syntax across all data types.
The Vector API allows developers to write code that takes advantage of CPU vector instructions. This can give performance improvements over normal loop-based code, especially in math-heavy operations. The idea is to get speed without dropping down into native code.
Java 25 adds more integration with native math libraries and supports automatic vectorization for certain floating-point tasks. There are also improvements around how data moves in and out of memory using memory segments. With updates like the Vector API for accelerated computations, Java continues to expand its relevance in advanced domains—including its rapidly growing significance for AI and machine learning use cases.
Java Flight Recorder can now collect CPU time profiling data on Linux. It uses kernel-level support to gather information safely and with low overhead. This helps developers see how much actual CPU time is spent inside Java code, rather than just wall-clock time.
It’s an experimental feature in this release, so it may not be available on all platforms yet. Still, it adds useful visibility for teams tuning performance on Linux servers.
The 32-bit x86 port has been removed from the JDK. It was marked for removal earlier and is now gone. The codebase no longer supports compiling or running Java on 32-bit x86 systems.
This change won’t affect most developers, since 64-bit systems have been the standard for years. But for any teams still maintaining older hardware, this is a breaking change.
Scoped values let a method pass data to other methods it calls, including tasks running in separate threads. Unlike thread-local variables, they are easier to manage and cheaper in terms of memory and performance. This feature is especially useful when working with virtual threads or structured concurrency. It avoids the usual pitfalls of shared state by keeping the data confined to a specific scope.
In this release, there’s a small but important change. The ScopedValue.orElse method no longer accepts null as a fallback. That adds a bit of safety and removes ambiguity from the API.
This new Java API provides a standard way to derive encryption keys from passwords or other secret data. Before this, many applications had to rely on external libraries or custom code. Now there’s a built-in solution that covers common key derivation algorithms like Argon2 and HMAC-based functions.
It supports both Java and native implementations, which means providers can optimize for different use cases. It also fits into higher-level security protocols that depend on predictable and secure key handling.
This feature lets you import everything exported by a module in a single line. It’s meant to simplify how developers use modular libraries without forcing them to write modular code themselves. It can help during development, especially in large projects that use many modules.
While this addition cuts down on repetitive import statements, it does come with some caution. If two modules export classes with the same name, it can lead to ambiguity, so it's something developers will need to keep in mind.
Small programs in Java are now easier to write. You no longer need to set up a full class structure just to print a message or test a simple function. This feature builds on earlier work that aimed to make Java more approachable for beginners and less verbose for experienced developers writing quick scripts or tools.
Instead of forcing a rigid structure, Java now accepts single-class source files with less boilerplate. It’s still Java under the hood, but with fewer barriers at the entry point.
Constructors are now a little more relaxed. You can write code before calling another constructor in the same class or the parent class. This wasn't allowed before. The idea is to let developers run safe setup code earlier without breaking object construction rules.
There’s a limit, though. You still can’t use the object itself before the constructor chain finishes. But you can do things like set fields or calculate values. This change just makes the structure less restrictive and more natural to write.
This update, along with other recent tooling improvements, reflects how Java tooling continues to evolve. The goal is to simplify startup optimization workflows without changing how the cache system works behind the scenes.
For most developers, this means faster startup can be achieved with less setup. The changes don’t introduce new AOT behavior, but they do remove friction from using it.
The JVM can now load method profiling data from previous runs at startup. This helps the just-in-time compiler make faster decisions about what to optimize. The result is reduced warm-up time and better early performance.
Nothing needs to change in your code. You just need to generate and reuse the AOT profiles between runs. This fits into the broader effort to make Java applications feel more responsive on launch.
This update makes thread sampling in Java Flight Recorder more stable. It does that by collecting data only at safe points in execution, which avoids interference with running code. The benefit is more accurate results with less overhead.
The feature is aimed at performance-sensitive environments where even small measurement delays or timing issues could distort results.
Java 25 reduces the size of object headers in memory from 96 or 128 bits down to 64 bits on 64-bit systems. This frees up memory space, which can lead to better cache use and faster access times.
The change applies across the JVM and affects all objects. It was optional in earlier versions but is now the default. It’s not something most developers will notice directly, but in large applications, the savings can add up.
This feature adds support for method-level timing and tracing in Java Flight Recorder without needing bytecode instrumentation. You can now track how long specific methods take to run and see call stacks during execution, all without changing source code.
It works through tools and APIs that let you choose which methods to observe. This gives more control over what gets recorded and helps developers focus on parts of the application that matter most.
Shenandoah now supports generational garbage collection as a full product feature. This makes it more competitive with collectors like G1 and ZGC. Generational mode helps by separating short-lived and long-lived objects, which improves throughput and reduces pause times.
The collector has seen enough testing since Java 24 to be considered stable. Teams already using Shenandoah can benefit right away, while others may want to evaluate it against existing GC options.
Java 25 removes support for the 32-bit x86 port. This was expected for a while, and it’s now official. The architecture is outdated, and maintenance costs no longer justify keeping it. If you still rely on it, there’s no workaround beyond upgrading your hardware or freezing your runtime.
Some deprecated options and internal APIs are being cleaned out too, but nothing that will catch most projects off guard. The removals are mostly in line with past patterns. Deprecated elements that have seen little or no usage are quietly dropped after enough time.
The language itself remains stable. No syntax is going away. Most of what’s being removed is behind the scenes, in parts of the JDK that are either legacy or no longer maintained. If you’ve kept up with past releases, you probably won’t notice anything missing.
Project Valhalla is a long-running OpenJDK project aimed at changing how Java represents data in memory. Right now, Java splits everything into two categories. You have primitives, which are fast and memory-efficient, and you have objects, which carry identity and add extra memory cost. Valhalla introduces something in between. It brings value classes, which look like regular objects in code but behave more like primitives under the hood.
This matters because performance in Java often hits a ceiling when dealing with large amounts of structured data. Objects take up more space, create more indirection, and add pressure on the garbage collector. Valhalla helps fix that by giving the JVM more control over how data is laid out in memory. With tighter packing and less overhead, programs can run faster and use memory more efficiently—without changing how Java code is written.
Java 25 carries early parts of Valhalla, though most are still in preview. This release doesn’t give you full value classes yet, but it moves the foundation forward. The JVM gets better at handling memory and object layout. Some internal changes, like compact headers and type metadata support, prepare the platform for what's coming next. These updates are not flashy, but they’re important steps toward the long-term goal.
You can already see pieces of Valhalla shaping the way Java deals with data and memory under the hood.
JVM support for compact object headers, reducing memory overhead for future value objects
Early internal changes to manage type metadata more efficiently
Updates that allow denser object layout, laying the path for better cache usage
New internal APIs to help with identity-free object handling
Overall, memory model shifts that favor performance and lower latency, especially for structured data
Valhalla is not complete. Java 25 includes several supporting features, but most are still in preview. Some parts are JVM-level changes you won’t see directly. Others, like value classes, are accessible behind preview flags. It's a gradual rollout. Java 25 isn’t the finish line, but it’s a clear sign of progress.
Let’s compare the current LTS version of Java 21 with the upcoming LTS version Java 25 and see exactly what has changed and improved.
Not much has changed in the surface syntax between the two versions, but some additions in Java 25 show how the language is starting to shift.
In Java 21, you can already use pattern matching with instanceof, like this:
if (obj instanceof String s) {
System.out.println(s.toLowerCase());
}
In Java 25, the same feature is more mature, and now you can pattern-match inside switch expressions with primitive types (in preview):
static String describe(int n) {
return switch (n) {
case 0 -> "zero";
case int x when x > 0 -> "positive";
case int x when x < 0 -> "negative";
default -> "unknown";
};
}
Java 21 didn’t change much about how objects live in memory. You still have headers, references, and object identity. With Java 25, that’s starting to shift. Features like Compact Object Headers and Scoped Values allow the JVM to manage memory with more control and less overhead, especially for short-lived or stateless data.
These changes aren’t always visible in code, but the performance difference can show up in data-heavy workloads. Here’s an example using the Scoped Values API:
ScopedValue<String> USER_ID = ScopedValue.newInstance();
void handleRequest() {
ScopedValue.where(USER_ID, "abc123").run(() -> {
log("User ID: " + USER_ID.get());
});
}
This avoids thread-local memory and offers cleaner, faster alternatives.
Structured concurrency was a preview feature in Java 21. In Java 25, it’s on its fifth round of previews. The goal is to simplify how concurrent tasks are launched, tracked, and cleaned up.
Here’s what that looks like now:
try (var scope = StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> fetchUser());
Future<String> profile = scope.fork(() -> fetchProfile());
scope.join();
scope.throwIfFailed();
String result = user.result() + " / " + profile.result();
System.out.println(result);
}
It reads more like sequential code but runs in parallel under the hood. Java 21 had this, but Java 25 improves performance and debugging.
Java 25 builds on what’s already there. It improves performance in the background, tightens up how the runtime works, and continues the slow shift toward cleaner and more flexible code. If you’re already on Java 21, you’re still in a good place. But if your codebase is on Java 11 or older, the gap is starting to show.
Modernizing doesn't have to be a big move. You can start small. Try newer features in isolated parts of your code. Keep an eye on what’s in preview. See how well your current tools and libraries support the latest version. Java 25 brings a few changes that will matter more in the long run than they might seem at first.
If your team is planning to upgrade or start something new, it helps to have developers who understand where Java is now. Not just the language itself but the way it works in modern systems. That’s a good reason to hire Java developers with recent experience.
Yes, Java 25 is an LTS release. That means it will receive long-term updates and support, making it a stable choice for production environments.
If you want access to the latest features, performance improvements, and memory optimizations, upgrading to Java 25 is worth considering. However, test compatibility before switching in production.
Value classes are identity-free types introduced under Project Valhalla. They behave like regular classes in code but are optimized for performance like primitives in memory.
Java 25 includes a mix. Some features are finalized, while others are still in preview, incubator, or experimental stages. You can opt in to try them using compiler flags.
Java 25 signals a shift toward more efficient memory handling, better performance, and cleaner APIs. It’s a good time to explore how modern Java can improve your architecture.
Get In Touch
Contact us for your software development requirements
Get In Touch
Contact us for your software development requirements