Java 25 vs Java 27: What Changed Between JDK 25 and JDK 27

  • Last Updated: September 27, 2026
  • By: javahandson
  • Series
img

Java 25 vs Java 27: What Changed Between JDK 25 and JDK 27

Java 25 vs Java 27 compared: G1 default GC, compact object headers, lazy constants, post-quantum TLS, support timelines and whether you should upgrade.

Java 25 vs Java 27 is a question many Java developers are asking right now. Java 27 reached general availability on 15 September 2026. However, most teams have only just moved to Java 25, the latest LTS release. So should you jump again, or should you stay where you are?

In this article, I will walk you through every important change between these two versions. We will look at garbage collection, memory, concurrency, security and the language itself. I will also show small code examples, so you can see the difference instead of just reading about it.

By the end, you will know exactly what changed and whether the upgrade makes sense for your project.

1. Quick Answer: Java 25 vs Java 27 at a Glance

If you are short on time, here is the summary first.

  • Java 25 is a Long-Term Support (LTS) release. It came out in September 2025, and Oracle gives it Premier Support until September 2030.
  • Java 27 is a short-term release. Oracle will update it only until March 2027, when Java 28 replaces it.
  • Java 27 makes G1 the default garbage collector in every environment, including tiny containers.
  • Java 27 turns on Compact Object Headers by default. In Java 25, you had to switch them on yourself.
  • Stable Values from Java 25 are now called Lazy Constants, and the API is smaller.
  • Structured Concurrency reached its seventh preview with some real API changes.
  • Java 27 adds post-quantum hybrid key exchange for TLS 1.3 without any code change.
  • JDK Flight Recorder now hides passwords and tokens in recordings by default.

In short, Java 27 is a solid step forward. Still, it is not a release you must rush into for production.

2. Release Timeline and Support: LTS vs Non-LTS

Before we compare features, let us understand where each version sits. This part matters more than most people think.

2.1 Java 25 is the current LTS

Java 25 became generally available on 16 September 2025. It is an LTS release, just like Java 17 and Java 21 before it.

According to Oracle’s support roadmap, Java 25 gets Premier Support until September 2030. Extended Support goes on until September 2033. That is why most banks, insurance firms and big enterprises pick LTS versions for production.

2.2 Java 27 is a six-month release

Java 27 is not an LTS release. Oracle will ship updates for it only until March 2027. After that, you must move to Java 28, and later to Java 29.

The next LTS after Java 25 should be Java 29, expected in September 2027. This follows the usual two-year LTS rhythm.

2.3 Don’t forget Java 26 in between

Here is something many comparison articles skip. When you move from 25 to 27, you also get everything from Java 26. Java 26 came out on 17 March 2026 and brought ten JEPs.

A few Java 26 changes you will feel directly:

  • HTTP/3 support in the built-in HttpClient (JEP 517).
  • Removal of the Applet API (JEP 504).
  • Warnings when code uses deep reflection to change final fields (JEP 500).
  • Better G1 throughput through less synchronization (JEP 522).
  • Ahead-of-time object caching with any GC (JEP 516).

So think of this upgrade as “Java 25 plus two releases of changes”, not just one.

Java 25 vs Java 27 release timeline showing Java 25 LTS, Java 26, Java 27 and the next LTS Java 29

3. Garbage Collection: G1 Becomes the Default Everywhere

This is one of the most practical changes in Java 27. It can quietly change how your small services behave.

3.1 How Java 25 picks a garbage collector

In Java 25, G1 is the default on “server-class” machines. However, the JVM still falls back to the Serial GC on small machines. The rule is simple. If the JVM sees a single CPU, or less than about 1792 MB of memory, it chooses Serial.

This matters a lot in Kubernetes. Many teams run small pods with one CPU and 1 GB of memory. On Java 25, those pods silently run with Serial GC. Serial pauses the whole application during every collection.

3.2 What Java 27 changes

JEP 523 removes this special case. Now the JVM always selects G1 when you do not choose a collector yourself. The OpenJDK team explains that recent work made G1 competitive with Serial even on small machines. G1’s throughput is now close to Serial, and its pauses are usually shorter.

You can check which GC your JVM picks with one simple command.

# Simulate a tiny container: 1 CPU and 256 MB heap
java -XX:ActiveProcessorCount=1 -Xmx256m -Xlog:gc --version

# Java 25 output (small machine):  Using Serial
# Java 27 output (any machine):    Using G1

If your service really performs better with Serial, you can still ask for it directly.

java -XX:+UseSerialGC -jar my-service.jar

A collector you pick on the command line always wins over the JVM’s default choice.

Interview Insight: A common question is “Which GC does Java use by default?” In Java 25, the answer is “G1 on server-class machines, Serial on small ones.” From Java 27 onwards, the answer is simply “G1, in every environment.” Mentioning the old 1-CPU or 1792 MB rule shows the interviewer that you understand container behaviour.

4. Compact Object Headers: Optional in Java 25, Default in Java 27

This change saves memory in almost every Java application. You get the benefit without touching a single line of code.

4.1 What is an object header?

Every Java object carries a small header. The JVM uses it to store the hash code, GC age, lock state and a pointer to the object’s class. On 64-bit systems, this header normally takes 12 bytes (96 bits).

That sounds tiny. But a typical application creates millions of small objects. Those extra bytes add up quickly.

4.2 Java 25 vs Java 27 behaviour

Project Lilliput shrinks the header to 8 bytes (64 bits). The journey happened in three steps:

  • Java 24 added Compact Object Headers as an experimental feature (JEP 450).
  • Java 25 made it a normal product feature (JEP 519). However, it stayed off by default.
  • Java 27 turns it on by default (JEP 534).

So on Java 25, you had to enable it manually. On Java 27, you would disable it only if something breaks.

# Java 25: opt in manually
java -XX:+UseCompactObjectHeaders -jar app.jar

# Java 27: already on. To go back to the old layout:
java -XX:-UseCompactObjectHeaders -jar app.jar

# Check the current value on any JDK
java -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders

4.3 What you should watch

For most apps, this is free memory. Services with many small objects, such as DTOs, map entries or event objects, gain the most.

Still, a few things deserve a quick test:

  • Tools that read raw object layouts, like some profilers or off-heap libraries, may need updates.
  • The compressed class pointer is smaller, so the JVM can address about four million classes. Normal applications never come close to this limit.
  • The -XX:UseCompressedClassPointers flag is now obsolete in Java 27, because the JVM always compresses class pointers. Remove it from your scripts.
  • Always compare heap usage and GC logs before and after the upgrade.

Interview Insight: Interviewers often ask how to reduce a Java service’s memory without code changes. Compact Object Headers is a strong answer. Explain that the header drops from 12 bytes to 8 bytes per object. Then add that it is opt-in on Java 25 and the default on Java 27.

5. Stable Values Become Lazy Constants

Java 25 introduced Stable Values as a preview API (JEP 502). The goal was simple. Give developers a value that behaves like a final field, but the JVM creates it only when first needed.

After feedback, the API got a new name and a smaller shape. In Java 26 it became Lazy Constants (JEP 526). Java 27 continues it as a third preview (JEP 531).

5.1 Java 25: StableValue

First, here is how lazy logger creation looked in Java 25.

// Java 25 (preview): compile and run with --enable-preview
public class OrderController {

    private final StableValue<Logger> logger = StableValue.of();

    Logger getLogger() {
        return logger.orElseSet(() -> Logger.create(OrderController.class));
    }

    void submitOrder(User user, List<Product> products) {
        getLogger().info("order started");
        // ... business logic
        getLogger().info("order submitted");
    }
}

This worked well. However, you still needed a helper method, and the orElseSet() name confused many people.

5.2 Java 27: LazyConstant

Now look at the same idea in Java 27.

// Java 27 (third preview): compile and run with --enable-preview
public class OrderController {

    private final LazyConstant<Logger> logger =
            LazyConstant.of(() -> Logger.create(OrderController.class));

    void submitOrder(User user, List<Product> products) {
        logger.get().info("order started");
        // ... business logic
        logger.get().info("order submitted");
    }
}

Notice the difference. You pass the recipe once, at the declaration. After that, you simply call get(). The first call runs the supplier, and every later call returns the same cached value.

In the third preview, the team removed isInitialized() and orElse(). This keeps the API focused on one job.

5.3 Lazy collections

Java 27 also gives lazy versions of common collections. In other words, the JVM computes each element only when someone asks for it.

List<Thumbnail> thumbnails = List.ofLazy(pageCount, this::renderThumbnail);
Map<String, BigDecimal> rates = Map.ofLazy(currencies, this::fetchRate);
Set<String> enabled = Set.ofLazy(candidates, this::isFeatureEnabled);

Set.ofLazy() is new in Java 27. The list and map versions arrived in Java 26.

Remember, this is still a preview feature. So avoid it in production code for now, because the API can change again.

6. Structured Concurrency: The Seventh Preview

Structured Concurrency lets you treat a group of related tasks as one unit of work. If one task fails, the others get cancelled. As a result, you avoid leaked threads and messy error handling.

It works beautifully with virtual threads. In Java 25, it was in its fifth preview (JEP 505). Java 27 brings the seventh preview (JEP 533).

6.1 Java 25 style

// Java 25 (fifth preview)
Response handle(long id) throws InterruptedException {
    try (var scope = StructuredTaskScope.open()) {
        Subtask<User> user   = scope.fork(() -> userService.find(id));
        Subtask<Order> order = scope.fork(() -> orderService.find(id));

        scope.join();   // throws the unchecked FailedException on failure
        return new Response(user.get(), order.get());
    }
}

6.2 What changed by Java 27

At first glance, the basic shape looks the same. But a few details changed, and they will affect your code:

  • join() now throws the familiar checked ExecutionException. Java 27 drops the custom unchecked FailedException.
  • Joiner gained a third type parameter for the exception type.
  • Java 27 also removes the awaitAll() joiner.
  • A new open() overload lets you configure the scope inline, for example with a timeout and a name.
// Java 27 (seventh preview)
Response handle(long id) throws InterruptedException, ExecutionException {
    try (var scope = StructuredTaskScope.open()) {
        var user  = scope.fork(() -> userService.find(id));
        var order = scope.fork(() -> orderService.find(id));

        scope.join();   // now throws the checked ExecutionException
        return new Response(user.get(), order.get());
    }
}

If you tried Structured Concurrency on Java 25, expect small compile errors after the upgrade. Mostly, you will fix throws clauses and catch blocks.

Interview Insight: Interviewers love asking why Structured Concurrency is better than a plain ExecutorService. The key point is lifetime. With an executor, a failed task does not stop its siblings automatically. With StructuredTaskScope, all child tasks finish or get cancelled before the try block ends. Also mention that it is still a preview API in both Java 25 and Java 27.

7. Primitive Types in Patterns, instanceof and switch

Pattern matching in Java started with objects. This feature extends it to primitive types like int, long and double.

In Java 25, it was the third preview (JEP 507). Java 27 has the fifth preview (JEP 532). The core idea did not change, but the rules became stricter.

// Preview feature: needs --enable-preview
int score = 82;

String grade = switch (score) {
    case int s when s >= 90 -> "excellent";
    case int s when s >= 75 -> "good";
    default -> "fail";
};

double value = 100;
if (value instanceof byte b) {
    // true, because 100 fits in a byte without losing information
    System.out.println("Fits in a byte: " + b);
}

The instanceof check with a primitive type answers one question. Can this value move to the target type without losing information? So 100 instanceof byte is true, but 0.1 instanceof float is false.

The fifth preview tightens the rules for “unconditional exactness” and dominance in switch. In simple words, the compiler now catches more mistakes where one case hides another. You may see new compile errors in code that compiled on Java 25 with preview enabled.

8. Security Improvements in Java 27

Java 27 has three security-related changes. Two of them work without any code change, which I really like.

8.1 Post-quantum hybrid key exchange for TLS 1.3

Quantum computers could one day break today’s key exchange algorithms. Attackers can record encrypted traffic now and decrypt it later. That threat is often called “harvest now, decrypt later”.

JEP 527 adds hybrid key exchange to TLS 1.3. It combines a classic algorithm with ML-KEM, a quantum-resistant algorithm that Java 24 introduced. The JDK supports three hybrid groups:

  • X25519MLKEM768 (enabled and preferred by default)
SecP256r1MLKEM768
SecP384r1MLKEM1024

On Java 25, you do not get this protection in TLS. On Java 27, any code that uses the standard javax.net.ssl APIs gets it automatically. That includes HttpClient and most HTTP libraries built on JSSE.

You can still control the groups when a partner system needs something specific.

# Choose the named groups through a system property
java -Djdk.tls.namedGroups="X25519MLKEM768,x25519,secp256r1" -jar app.jar
// Or configure them in code
SSLParameters params = sslSocket.getSSLParameters();
params.setNamedGroups(new String[] {"X25519MLKEM768", "x25519"});
sslSocket.setSSLParameters(params);

From my payments integration experience, this is worth testing early. Some older bank gateways and load balancers may not handle the larger key shares well. So test your mTLS partners in a staging environment first.

8.2 PEM encodings: third preview

Loading a PEM key in plain Java has always been painful. You strip headers, decode Base64 and then call a key factory.

The PEM API was a first preview in Java 25 (JEP 470). Java 27 brings the third preview (JEP 538) with a few renames. In Java 25, the raw PEM data came back as a record called PEMRecord. In Java 27, it is a final class called PEM. Also, the DEREncodable interface got a new name, BinaryEncodable.

// Preview API: needs --enable-preview
PrivateKey key = PEMDecoder.of()
        .withDecryption(passphrase.toCharArray())
        .decode(pemString, PrivateKey.class);

String encoded = PEMEncoder.of()
        .withEncryption(passphrase.toCharArray())
        .encodeToString(key);

8.3 JFR in-process data redaction

JDK Flight Recorder is a great tool for production profiling. However, recordings capture command-line arguments, system properties and environment variables. Those often contain passwords, API keys or tokens.

On Java 25, all of this goes into the .jfr file as plain text. Java 27 (JEP 536) hides values whose names match common secret patterns, such as *password*, *token*, *secret* and *api*key*.

# Add your own pattern on top of the defaults
java -XX:FlightRecorderOptions:'redact-key=+*confidential*' -jar app.jar

# Turn redaction off completely (not recommended)
java -XX:FlightRecorderOptions:'redact-key=none,redact-argument=none' -jar app.jar

Overall, this small change makes it much safer to share recordings with vendors or support teams.

9. Features That Stayed the Same

Not everything moved forward. It helps to know what did not change, too.

  • Vector API is still incubating. Java 27 has the twelfth incubator (JEP 537), and it has no real changes since Java 25. The team waits for value classes from Project Valhalla before moving it to preview.
  • Final features from Java 25 work the same way in Java 27. These include Scoped Values, Module Import Declarations, Compact Source Files and Flexible Constructor Bodies.

So your Java 25 code that uses only final features should compile on Java 27 without drama.

10. Removals and Breaking Changes to Check

Every upgrade removes something. So, between Java 25 and Java 27, check these points carefully:

  • Applet API removed in Java 26. Old desktop code that still imports java.applet will not compile.
  • Final field warnings from Java 26. If a library uses reflection to change final fields, you will see warnings at runtime. Serialization and mocking libraries are common suspects.
  • ThreadPoolExecutor.finalize() removed in Java 27. Subclasses that override or call it need a fix.
  • java.locale.useOldISOCodes removed in Java 27. The old language codes like “iw” and “in” no longer come back through this property.
  • Old launcher options removed in Java 27. Now the JVM no longer accepts -noverify, -Xverify:none, -noclassgc and -verifyremote. Old startup scripts often still carry -noverify, so search for it.
  • JVMCI removed in Java 27. The experimental JVM Compiler Interface is gone. In practice, this mainly affects teams that plugged in an external JIT compiler.
  • VFORK launch mechanism removed in Java 27. The -Djdk.lang.Process.launchMechanism=VFORK option no longer works on Linux.
  • G1 on small containers. Your one-CPU pods now run G1 instead of Serial. Watch memory use and pause times after the switch.
  • Compact headers by default. Test any library that reads object layouts directly.

11. Java 25 vs Java 27 Comparison Table

Here is a side-by-side view of the most important differences.

AreaJava 25Java 27
Release typeLTS (Sept 2025)Non-LTS (Sept 2026)
Oracle Premier SupportUntil Sept 2030Until March 2027
Default GC on small machinesSerial (1 CPU or under ~1792 MB)G1 everywhere
Compact Object HeadersAvailable, off by defaultOn by default
Lazy initialization APIStableValue (1st preview)LazyConstant (3rd preview)
Structured Concurrency5th preview, FailedException7th preview, ExecutionException
Primitive patterns3rd preview5th preview, stricter checks
Post-quantum TLS 1.3Not availableHybrid ML-KEM on by default
PEM API1st preview3rd preview
JFR secret redactionNoYes, by default
HTTP/3 in HttpClientNoYes (added in Java 26)
Vector API10th incubator12th incubator

12. Should You Upgrade from Java 25 to Java 27?

This is the real question. My honest answer depends on where your code runs.

12.1 Stay on Java 25 if

  • Your application runs in production and needs long, stable support.
  • Company policy allows only LTS versions, which is common in banking and insurance.
  • Your frameworks or monitoring agents have not certified Java 27 yet.
  • Upgrading again every six months sounds like too much work for your team.

12.2 Try Java 27 if

  • Many small containers run your services, and you want lower memory use without code changes.
  • Quantum-resistant TLS or safer JFR recordings matter to you today.
  • Your team wants to test preview features like Lazy Constants and Structured Concurrency.
  • Moving to Java 28 in March 2027 feels comfortable for your team.

A practical middle path works well for many teams. Keep production on Java 25. Then run your CI build on Java 27 as well. This way, you catch breaking changes early, and the move to Java 29 LTS becomes much easier.

13. Common Mistakes When Moving from Java 25 to Java 27

I have seen teams make these mistakes with almost every upgrade. Avoid them and your migration will go smoothly.

  • Ignoring Java 26 changes. You skip a release number, but you do not skip its changes.
  • Using preview APIs in production. Stable Values code from Java 25 breaks on Java 27 because the API got a new name.
  • Forgetting –enable-preview at both compile time and runtime. You need it for javac and for java.
  • Not checking GC logs. The switch from Serial to G1 in small pods can change memory use.
  • Skipping TLS testing with partners. Hybrid key exchange sends larger handshake messages, so older middleboxes may complain.
  • Assuming all libraries are ready. Update build plugins, bytecode tools like ASM, and agents before you switch.
# Compiling and running preview code on Java 27
javac --release 27 --enable-preview Main.java
java --enable-preview Main

14. FAQ’s on Java 25 vs Java 27

Q: Is Java 27 an LTS release?

A: No. Java 27 is a non-LTS release. Oracle provides updates for it only until March 2027, when Java 28 replaces it. Java 25 is the current LTS, and Java 29 is expected to be the next LTS in September 2027.

Q: What is the biggest difference between Java 25 and Java 27?

A: The biggest practical changes are runtime changes. Java 27 makes G1 the default garbage collector in every environment and turns on Compact Object Headers by default. It also adds post-quantum hybrid key exchange for TLS 1.3 and JFR secret redaction.

Q: Which garbage collector does Java 27 use by default?

A: Java 27 uses G1 by default in all environments (JEP 523). In Java 25, the JVM still chose Serial GC on machines with a single CPU or less than about 1792 MB of memory. You can still select Serial with -XX:+UseSerialGC.

Q: Are Compact Object Headers enabled by default in Java 25?

A: No. In Java 25, Compact Object Headers are a product feature, but you must enable them with -XX:+UseCompactObjectHeaders. Java 27 turns them on by default, shrinking the object header from 12 bytes to 8 bytes on 64-bit systems.

Q: What happened to Stable Values from Java 25?

A: The Stable Values preview API was renamed to Lazy Constants in Java 26. Java 27 continues it as a third preview with a smaller API: you create a LazyConstant with LazyConstant.of(supplier) and read it with get().

Q: Do I need code changes to get post-quantum TLS in Java 27?

A: No. Java 27 enables the X25519MLKEM768 hybrid key exchange by default for TLS 1.3. Any code using the standard javax.net.ssl APIs benefits automatically. You can still customise the groups with the jdk.tls.namedGroups system property.

Q: Should I upgrade production from Java 25 to Java 27?

A: For most production systems, stay on Java 25 LTS and test Java 27 in CI. Upgrade to Java 27 only if you need its runtime or security gains now and your team is ready to move to Java 28 in March 2027.

15. Conclusion

The Java 25 vs Java 27 comparison comes down to stability versus fresh improvements. Java 25 is the safe, long-supported base for production. Java 27 gives you real runtime gains, like G1 everywhere and smaller object headers, plus useful security upgrades.

Most preview features, however, are still moving. So treat Java 27 as a great release to test and learn from, rather than something to force into production. If you plan well, you can enjoy these improvements today and land smoothly on Java 29 LTS next year.

16. Further Reading

Leave a Comment