Java 27 Features Explained: What’s New

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

Java 27 Features Explained: What’s New

Java 27 features explained in plain language — post-quantum TLS, G1 by default, compact object headers, structured concurrency, lazy constants, and more.

Java 27 is here, and it landed right on schedule on September 15, 2026. If you have been following Java for a while, you know the drill by now. A new version shows up every six months, and this one keeps that streak going. So let us walk through the main Java 27 features together, in plain language, with small examples you can actually read.

Java 27 does not have one standout feature. Unlike earlier versions that introduced major changes like records or virtual threads, this update may seem less exciting at first glance. If you’re only looking for flashy new syntax, you might just overlook it.

That would be a mistake. This release quietly improves the things that matter most in real applications. Think security, memory use, garbage collection, concurrency, and diagnostics. A lot of it helps your app just by upgrading the JVM, without you touching a single line of code.

The release ships nine JDK Enhancement Proposals, better known as JEPs. Four of them are preview features. One is still an incubating API. The rest are ready to use as normal, standard parts of the JDK.

The Nine JEPs at a Glance

Before we dig in, here is the full list. Keep this handy as a map for the rest of the article.

JEPFeatureStatus
JEP 527Post-Quantum Hybrid Key Exchange for TLS 1.3Standard
JEP 538PEM Encodings of Cryptographic ObjectsThird Preview
JEP 532Primitive Types in Patterns, instanceof, and switchFifth Preview
JEP 523Make G1 the Default Garbage Collector EverywhereStandard
JEP 534Compact Object Headers by DefaultStandard
JEP 531Lazy ConstantsThird Preview
JEP 533Structured ConcurrencySeventh Preview
JEP 537Vector APITwelfth Incubator
JEP 536JFR In-Process Data RedactionStandard

Notice the pattern in that table. Java is getting more secure by default. It is getting more memory-friendly on its own. And it keeps smoothing out the rough edges in the language. Now let us look at each feature one by one.

1. Post-Quantum Hybrid Key Exchange for TLS 1.3 (JEP 527)

One of the biggest changes in Java 27 is also one you may never see. It works behind the scenes, and that is exactly the point.

Most of us use TLS every day without thinking about the maths underneath. When your Spring Boot service calls another HTTPS service, Java negotiates the encryption and sets up a safe channel for you. You never touch the algorithms yourself.

1.1 The Quantum Problem in Simple Terms

So what is the worry here? Quantum computers, once they grow powerful enough, could break some of the public-key algorithms we lean on today. That day may be years away, but the risk is real.

Some attacks are sneaky. One type is called “harvest now, decrypt later.” Here, an attacker collects your encrypted data today, even if they can’t read it yet. They save it for later. Years later, if they have a powerful quantum computer, they will try to crack it open.

Java 27 starts fighting back at the TLS layer. Instead of trusting one traditional algorithm alone, it can use a hybrid key exchange. This mixes a classic elliptic-curve algorithm with the post-quantum ML-KEM algorithm.

The clever bit is that safety no longer rests on a single approach. You get the old and the new working side by side. If one weakens over time, the other still stands guard.

1.2 What This Looks Like in Practice

Java 27 supports three hybrid groups for TLS. Here they are:

X25519MLKEM768
SecP256r1MLKEM768
SecP384r1MLKEM1024

Out of these three, X25519MLKEM768 is turned on by default. That default matters a lot. Many Java apps will get this protection for free, with zero code changes.

If both ends of a TLS 1.3 connection support the hybrid group, Java’s existing javax.net.ssl machinery just picks it automatically. You do not rewrite your HTTP clients. You do not hand-roll any cryptography.

Do you need finer control? You can still set the supported groups yourself:

SSLParameters.setNamedGroups(...)

Or you can use a system property instead:

jdk.tls.namedGroups

For most enterprise developers, the important point is simple. Java’s TLS is preparing for a future where quantum computers are common. This approach is the best way to enhance security. With one update to the Java Virtual Machine (JVM), millions of apps can improve instead of each team having to create their own encryption.

💡 Interview Insight

A likely question: “How does Java 27 defend against future quantum attacks in TLS?” Keep it crisp. Java 27 adds hybrid key exchange for TLS 1.3, combining a classic elliptic-curve algorithm with the post-quantum ML-KEM algorithm. The group X25519MLKEM768 is on by default, so apps gain protection without code changes. Mention “harvest now, decrypt later” to show you understand why this matters today, not just in the future.

2. PEM Encodings of Cryptographic Objects (JEP 538)

Java has handled cryptography for decades. Yet one very common format has always been a bit painful to deal with directly. That format is PEM.

You have almost certainly seen files that look like this:

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

Or files that hold a key, like this:

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

PEM shows up all over security infrastructure. Certificates, public and private keys, certificate authorities, OpenSSL tools, cloud setups, DevOps pipelines. It is everywhere.

But turning Java security objects into PEM, and back again, has never been smooth. Usually you reach for an extra library, or you write your own Base64 juggling, or you copy some boilerplate off the internet.

2.1 A Standard API for a Common Job

JEP 538 brings a proper Java API for this task. It is now in its third preview. The API centres on two simple ideas:

PEMEncoder
PEMDecoder

You no longer need to remove headers, manually decode Base64, create key specifications, and provide them to a KeyFactory. Java can now directly read and write common PEM formats.

Encoding looks roughly like this:

PEMEncoder encoder = PEMEncoder.of();
String pem = encoder.encodeToString(certificate);

Decoding runs the other way:

PEMDecoder decoder = PEMDecoder.of();
X509Certificate certificate =
        decoder.decode(pem, X509Certificate.class);

The API covers the important objects: keys, certificates, and certificate revocation lists. The third preview also keeps polishing things. One example is the BinaryEncodable idea, used for objects whose binary form can turn into PEM.

2.2 Why You Will Care

Picture a few common jobs on a real team:

  • A microservice pulls a certificate from a secrets manager.
  • A Spring Boot service loads private keys handed over by an infra team.
  • An app swaps certificates with some OpenSSL-based tooling.

PEM plays a central role in cryptographic processes. By incorporating a standard Java Development Kit (JDK) API, the need for manual parsing and reliance on third-party libraries for common operations is significantly reduced. This reduction is crucial for enhancing safety, as minor parsing errors in cryptographic code can lead to substantial security vulnerabilities. Integrating these features into the platform facilitates safer interoperability.

One caution. This is still a preview API. Feel free to try it, but do not treat its shape as final just yet.

💡 Interview Insight

Expect: “What problem does the PEM API in Java 27 solve?” Answer that before it, converting Java keys and certificates to and from PEM meant extra libraries or manual Base64 handling. JEP 538 adds PEMEncoder and PEMDecoder as a standard API for keys, certificates, and CRLs. Add that it is a preview feature, so the API can still change.

3. Primitive Types in Patterns, instanceof, and switch (JEP 532)

Pattern matching has slowly changed how we write modern Java. It started small and grew useful fast.

First Java gave instanceof a pattern variable. Then came pattern matching for switch, record patterns, and more. Each step made branching code shorter and clearer.

But one gap stayed open. Pattern matching mostly worked with reference types. Primitive values like int, long, float, double, and boolean played by different rules. JEP 532 keeps closing that gap. In Java 27 it reaches its fifth preview.

3.1 A First Look at Primitive Patterns

Take this small example:

double value = 42.0;

if (value instanceof int i) {
    System.out.println("Exact integer value: " + i);
}

At first glance that might feel odd. For years, instanceof lived entirely in reference-type territory:

if (obj instanceof String s) {
    ...
}

With primitive patterns, Java can now check whether a primitive conversion is safe and exact. Look at the difference below.

double value = 42.5;

That value cannot safely match an int pattern. Converting 42.5 to 42 would throw away the fraction. But this one is fine:

double value = 42.0;

It represents a whole number exactly. So the match succeeds. This lets pattern matching handle safe primitive conversions for you, instead of you casting by hand and risking data loss.

3.2 Bringing switch Along

The feature also stretches switch. Here is a tidy example:

static String temperature(double value) {
    return switch (value) {
        case 0.0 -> "Freezing";
        case 100.0 -> "Boiling";
        case double d when d < 0 -> "Below freezing";
        case double d -> "Normal range: " + d;
    };
}

This ties into a bigger language goal. Java wants to shrink the pointless gaps between primitive values and reference values.

For a long time we have hopped between two worlds. On one side sit the primitives:

int
long
double
boolean

On the other side sit their wrapper types:

Integer
Long
Double
Boolean

Generics, object APIs, pattern matching, and nullability exhibit distinct behaviors depending on the programming environment being utilized. Major initiatives, such as Project Valhalla, are focused on unifying the type systems over time to create a more cohesive experience. In this context, primitive patterns align well with the overarching goals of the project.

Java 27’s fifth preview also tightens the rules around exactness and dominance checks in switch. The goal is not more syntax for its own sake. It is safer, more predictable pattern matching across the whole type system.

3.3 How to Try It

You may not rewrite big chunks of business code around this right away. Its value is more architectural. The language is becoming more uniform, and that pays off everywhere over time.

Since this is a preview feature, you must enable preview flags to use it:

javac --enable-preview --release 27 Example.java
java --enable-preview Example

💡 Interview Insight

A common question: “What do primitive type patterns add to instanceof and switch?” They let pattern matching work with primitives such as int and double, and they check whether a conversion is exact before it happens, so 42.0 matches an int pattern but 42.5 does not. Note it is a preview feature and part of the broader push, alongside Project Valhalla, to unify primitives and references.

4. G1 Becomes the Default Garbage Collector Everywhere (JEP 523)

Garbage collection is one of Java’s superpowers. You create objects, and the JVM works out when the unused ones can be cleared away. You rarely think about it, which is the whole idea.

Java ships several collectors, each tuned for different jobs. You have G1, Serial GC, Parallel GC, ZGC, and a few others. For years the JVM picked a default based on the environment it found itself in.

4.1 What Changed

In typical server environments, the G1 garbage collector has often been the preferred default choice due to its efficiency and performance. However, in smaller setups, such as machines with limited CPUs or minimal memory, the Java Virtual Machine (JVM) may automatically select the Serial Garbage Collector instead. This decision is made to optimize resource usage on systems with constrained capabilities.

Java 27 changes that. With JEP 523, G1 becomes the default in every environment, as long as you have not chosen a collector yourself. So this command:

java MyApplication

now uses G1 whether it runs on a big server, your laptop, a tiny container, or a cramped virtual machine.

4.2 Why This Is a Good Move

There are two notable reasons for this trend. Firstly, the G1 garbage collector has consistently improved with each release. Its performance and memory requirements have reached a level where it is typically unnecessary to switch to Serial GC on smaller systems, as G1 now performs adequately for most applications.

Secondly consistency is crucial in application deployment and management. When you develop and test a service in a local environment, then package it into a container with strict resource limits, maintaining uniformity becomes essential. Historically, significant differences in resource availability could lead the Java Virtual Machine (JVM) to automatically switch to a different garbage collector, which could affect performance and stability. Ensuring consistency in your environment helps mitigate such issues and allows for more predictable behavior across different stages of development and deployment.

That meant the same app, launched the same way, could behave differently across environments. Java 27 makes the rule dead simple:

No collector specified means G1. Every time.

To be clear, Serial GC is not gone. If your app does better with it, just ask for it directly:

-XX:+UseSerialGC

The same holds for the other collectors. This change is about the default, not about taking away your choice.

Most server-class apps already run G1, so they will barely notice. The teams to watch are those running tiny JVM processes, small containers, or command-line tools where Serial GC used to kick in. Those should benchmark Java 27 under real CPU and memory limits, rather than assuming nothing changed.

💡 Interview Insight

Likely question: “What does JEP 523 change about garbage collection?” G1 is now the default collector in all environments, not just server-class ones. Before, constrained setups could fall back to Serial GC automatically. The win is consistency: the same app behaves the same way across laptop, staging, and small containers. You can still opt into Serial GC with -XX:+UseSerialGC.

5. Compact Object Headers Become the Default (JEP 534)

This one may be the most practical Java 27 feature for apps that churn out lots of objects. And you get it for free.

In Java, every object contains not only the fields that you explicitly define but also additional information added by the Java Virtual Machine (JVM). This extra information, known as metadata, is stored in a component called the object header. The object header plays an essential role in managing the object’s lifecycle and provides the JVM with crucial details about the object.

Say you write this tiny class:

class Customer {
    int id;
}

A Customer object is not just four bytes for that int. The JVM also needs room for locking info, garbage-collection state, an identity hash code, the object’s class, and other runtime bits.

5.1 From 96 Bits Down to 64

In traditional 64-bit Java Virtual Machines (JVMs), object headers typically occupy 96 bits, equivalent to 12 bytes. However, with the introduction of compact object headers, this size is reduced to 64 bits, or 8 bytes. Notably, Java 27 has made this compact header layout the default configuration for the HotSpot JVM, optimizing memory usage.

Four bytes difference sounds tiny. But think about how apps really behave. A big Spring Boot service can hold millions of objects. Data-heavy apps can hold tens or hundreds of millions.

Domain entities, caches, linked structures, JSON models, ORM-managed objects, framework metadata. They all pile up fast. Suppose your app holds ten million objects and saves roughly four header bytes on many of them. At the header level that is about:

10,000,000 × 4 bytes
≈ 40 MB

Real savings depend on object alignment and layout, so do not assume every object drops exactly four bytes. Still, across object-heavy workloads, the total memory saved can be large.

5.2 The Ripple Effects

Smaller objects bring bonus wins beyond raw memory:

  • More objects fit into CPU caches.
  • More objects fit inside the same heap regions.
  • Less memory has to be moved or scanned during GC.
  • Each JVM may need less memory, so container density improves.

Oracle frames this not just as a smaller heap, but as a boost to data locality and deployment density. If testing ever turns up a problem, you can switch the old layout back on:

-XX:-UseCompactObjectHeaders

For most of us, though, the best part is that there is nothing to rewrite. Upgrade the JVM, and your objects simply take less space. Existing code gets more efficient without any redesign.

💡 Interview Insight

A sharp question: “How do compact object headers save memory in Java 27?” They cut the object header on 64-bit HotSpot from 96 bits (12 bytes) to 64 bits (8 bytes), now on by default. For object-heavy apps with millions of objects, that adds up to real heap savings, plus better cache use and deployment density. Mention you can revert with -XX:-UseCompactObjectHeaders if needed.

6. Lazy Constants (JEP 531)

Apps often hold data that acts like a constant but is expensive to build. Think of a big model or a heavy lookup table.

Here is the classic way to do it:

private static final SearchModel MODEL = loadModel();

This is simple, thread-safe, and immutable from your app’s point of view. The trouble is timing.

6.1 The Timing Trap

The loadModel() function is executed during the initialization of the class. If the model loading process is slow, it can significantly delay the startup time of your application. This delay occurs even if the application does not ultimately utilize the model at all.

The usual fix is lazy initialization, which often looks like this:

private static SearchModel model;

static synchronized SearchModel getModel() {
    if (model == null) {
        model = loadModel();
    }
    return model;
}

The implementation functions effectively, but the addition of synchronization and mutable state introduces the potential for subtle bugs. Utilizing holder classes and suppliers can also be beneficial. However, Java lacks a straightforward mechanism to express the concept of a constant value that is only initialized the first time it is required.

6.2 A Cleaner Answer

JEP 531 gives us exactly that, with Lazy Constants. It is now in its third preview. The code reads cleanly:

private static final LazyConstant<SearchModel> MODEL =
        LazyConstant.of(SearchModel::load);

static Result search(String query) {
    return MODEL.get().search(query);
}

The distinction between a standard Supplier and a lazy constant lies in their functionality and purpose. A Supplier is designed to execute its operation multiple times, generating new outputs each time it is called. In contrast, a lazy constant represents a single, fixed value. Once the initializer assigns this value, it remains unchanged, providing the same immutable content with every subsequent request.

The JVM can also treat this as constant-like data. That opens the door to optimizations similar to those it applies to final fields. Thread safety comes built in as well. If several threads reach an uninitialized lazy constant at once, the initialization is coordinated for you. No hand-rolled double-checked locking needed.

6.3 Why It Fits Cloud-Native Apps

Lazy constants shine in cloud setups. Modern services restart, autoscale, and spin up just to handle a burst of work. Slow startup and wasted initialization cost real money there.

Applications often manage costly resources such as lookup tables, model metadata, parsers, or configuration trees, which may only be necessary for specific requests. Utilizing lazy constants allows developers to defer the loading and associated costs of these resources until the point at which they are actually required. With the release of Java 27, this concept has been expanded to include lazy implementations of immutable collections, such as lists, maps, and sets, enhancing efficiency and resource management within applications.

It stays in preview because the platform team is still gathering real-world experience with the API. But the idea fills a gap developers have worked around for years: late initialization without losing constant semantics.

💡 Interview Insight

Expect: “How is a lazy constant different from a Supplier?” A Supplier can run its logic repeatedly and may return different results. A lazy constant represents one value, initialized on first use and then fixed forever. The JVM can optimize it like a final field, and thread-safe initialization is handled for you. It is a preview feature in Java 27, currently in its third preview.

7. Structured Concurrency (JEP 533)

Virtual threads have addressed a significant scaling challenge in programming. They allow developers to create a large number of threads without the overhead of allocating one operating system thread for each task. However, even with the efficiency of these lightweight threads, writing and managing concurrent code remains complex and can lead to difficulties in understanding and maintaining it.

7.1 A Familiar Headache

Say an endpoint needs three separate pieces of data:

Customer details
Recent orders
Recommended products

Fetching them one after another is slow. The natural fix is to run them at the same time. But concurrency brings a pile of hard questions:

  • What happens when one task fails?
  • Should the other tasks keep going?
  • How do you cancel them cleanly?
  • Who actually owns those tasks?
  • Can a child task outlive the request that started it?
  • How do you make sure every task finished before returning?

Older tools like ExecutorService, Future, and CompletableFuture give you the raw parts. But you end up building most of the task-lifecycle structure by hand.

7.2 Tasks as One Unit of Work

Structured concurrency flips the model. JEP 533 reaches its seventh preview in Java 27. The core idea is simple: concurrent tasks that belong to one logical operation should be treated as one unit of work.

To ensure that background tasks remain organized and do not stray from their intended purpose, it’s beneficial to run them within a defined scope. This approach provides clarity and structure, allowing for easier management and monitoring of the tasks. A simplified pattern for implementing this concept can be outlined as follows:

try (var scope =
        StructuredTaskScope.open()) {

    var customer = scope.fork(() -> loadCustomer());
    var orders = scope.fork(() -> loadOrders());
    var recs = scope.fork(() -> loadRecommendations());

    scope.join();  // wait for all as one unit

    return combine(customer.get(), orders.get(), recs.get());
}

The scope defines the duration of an entire operation, during which tasks are initiated. The parent process waits for these tasks to complete using the join() function. Once the scope concludes, it’s guaranteed that all child threads will have finished executing, ensuring that no tasks are left running in the background.

This mirrors structured programming itself. Long ago, control flow could jump around wildly. Methods, loops, and blocks gave code clear boundaries. Structured concurrency brings that same discipline to threads.

7.3 Joiners and Policies

Java 27’s API uses joiners to describe how subtask results combine. Different jobs need different rules:

  • Sometimes every subtask must succeed.
  • Sometimes the first success is all you need.
  • Sometimes you stop as soon as a condition is met.

The scope expresses these policies directly, so cancellation and error handling do not get sprinkled all over your code. In Java 27, the standard joiners throw an ExecutionException on failure, with the original cause reachable through getCause(). The join() method also carries a clearer exception type in its signature.

7.4 Even Better With Virtual Threads

This gets really powerful next to virtual threads. Here is the split:

  • Virtual threads answer, “How can Java cheaply run many tasks at once?”
  • Structured concurrency answers, “How should those related tasks be organized?”

Together, they create a simpler way to manage server tasks. Imagine a Spring Boot endpoint that calls five other services at the same time. Virtual threads make these blocking calls inexpensive. Structured concurrency groups the five calls into one request. If the request gets canceled, fails, or times out, all five are handled together.

Observability improves too. A thread dump can show the parent-child shape of your tasks, instead of a flat wall of unrelated threads. For production debugging, that alone is a big help.

Why still preview after seven rounds? Concurrency APIs are brutally hard to change once they are frozen. The team keeps refining based on feedback rather than rushing to declare it done. That patience is a good thing. You can experiment with it now, especially with virtual threads, but remember a preview API can still shift.

💡 Interview Insight

A strong question: “What does structured concurrency add on top of virtual threads?” Virtual threads make many concurrent tasks cheap; structured concurrency organizes related tasks as one unit of work inside a scope, so failure, cancellation, and cleanup happen as a group. It is a seventh-preview feature in Java 27, and standard joiners now throw ExecutionException on failure. Mention better observability in thread dumps as a bonus.

8. Vector API (JEP 537)

Modern CPUs utilize a technique known as SIMD, or Single Instruction, Multiple Data, which allows them to perform the same mathematical operations on multiple values simultaneously. This capability enhances processing efficiency and performance, particularly in tasks that involve large data sets, such as multimedia processing and scientific computations.

Say you want to add two arrays:

A = [1, 2, 3, 4]
B = [5, 6, 7, 8]

A plain scalar loop does them one at a time:

1 + 5
2 + 6
3 + 7
4 + 8

Vector-capable hardware can crunch several of those in a single instruction. That is a big deal for machine learning, scientific computing, image and audio processing, analytics, financial maths, and signal processing.

8.1 Writing Vectors On Purpose

The Java Virtual Machine (JVM) has the capability to automatically vectorize certain code snippets. However, compilers may not always recognize when a standard scalar loop can be transformed into a more efficient SIMD (Single Instruction, Multiple Data) operation. The introduction of the Vector API allows developers to explicitly define vectorized operations while still writing in standard Java, enhancing performance and optimizing code execution.

In Java 27, JEP 537 delivers the twelfth incubating version of the Vector API. It works with ideas like vector species, lanes, masks, and vector operations. A conceptual example:

var species = FloatVector.SPECIES_PREFERRED;

for (int i = 0; i < length; i += species.length()) {
    var a = FloatVector.fromArray(species, left, i);
    var b = FloatVector.fromArray(species, right, i);

    a.add(b).intoArray(result, i);
}

You do not write raw AVX or NEON instructions by hand. The JVM maps your vector operations onto the right hardware instructions when the platform supports them. Java stays portable, and you still tap into modern CPU speed.

8.2 Why Still Incubating?

Twelve rounds sounds like a lot. The hold-up is not just method names. The final design ties into deeper changes in Java’s object and value model, largely through Project Valhalla.

Locking the API before those foundations settle could trap Java with a weaker design forever. Since it is an incubating API, you must enable the module explicitly:

--add-modules jdk.incubator.vector

Most ordinary Spring Boot apps will never touch the Vector API directly. But libraries and number-crunching apps should watch it closely. Java has long trailed native languages for heavy SIMD work. A mature Vector API could close much of that gap while keeping Java’s portability, safety, garbage collection, and tooling.

💡 Interview Insight

You might get: “What is the Vector API and why is it still an incubator?” It lets you write explicit SIMD-style computations in portable Java, which the JVM maps to hardware instructions like AVX or NEON. It reaches its twelfth incubator in Java 27 and stays non-final because its design depends on Project Valhalla’s value types. Enable it with –add-modules jdk.incubator.vector.

9. JFR In-Process Data Redaction (JEP 536)

Java Flight Recorder (JFR) is an advanced tool for production diagnostics within the Java Virtual Machine (JVM). It provides in-depth insights into various performance aspects, including CPU usage, garbage collection processes, thread activities, memory allocations, lock behaviors, and input/output operations. One of its key advantages is its ability to gather detailed information while maintaining minimal overhead, making it an effective resource for monitoring and optimizing Java applications in real-time.

Because those recordings are so useful, people download and share them. They go to developers, support teams, performance engineers, even outside vendors. And that creates a problem.

9.1 Secrets Hiding in Diagnostics

Diagnostic data can hold sensitive config. Imagine starting an app like this:

-Ddatabase.password=superSecret
-Dapi.token=xyz123

Or storing secrets in environment variables such as:

DATABASE_PASSWORD
API_SECRET
AUTH_TOKEN

A recording that captures this could leak secrets to people who only wanted performance numbers. That is a nasty accident waiting to happen.

9.2 Redaction Inside the Process

JEP 536 introduces in-process redaction for Java Flight Recorder (JFR), enhancing the security of sensitive data. In Java 27, the ability to remove sensitive command-line arguments, as well as the initial values of environment variables and system properties, ensures that this information is safeguarded before it exits the Java Virtual Machine (JVM) process.

That last part is the key. This is not just a display trick in JDK Mission Control that hides a value on screen. The sensitive value can be removed before it is written into the recording in the clear.

Java 27 ships with default redaction and also lets you tune it. You can add your own key patterns:

-XX:FlightRecorderOptions:redact-key=+<filter>

And you can filter command-line arguments too:

-XX:FlightRecorderOptions:redact-argument=+<filter>

You can even disable the default filters when needed. But think hard before doing that in any place where JFR files get shared around.

This lands on a recurring Java 27 theme: secure defaults. Rather than trusting every ops team to remember and scrub secrets by hand, the JVM keeps them out of the recording in the first place. For shops that lean on JFR in production, this may be one of the most useful operational wins in the release.

💡 Interview Insight

Likely: “What does JFR in-process redaction protect against?” It stops secrets in command-line arguments, environment variables, and system properties from leaking into JFR recordings. The redaction happens inside the process before the data leaves, not just in the viewer. It is a standard feature in Java 27 with default filters plus configurable key and argument patterns.

10. Java 27 Includes More Than the Nine JEPs

The nine JEPs grab the spotlight. But a Java release always carries a crowd of smaller fixes that do not need their own JEP. Java 27 is full of them, spread across security, diagnostics, JIT compilation, garbage collection, I/O, tooling, and core libraries.

10.1 Handy Operational Additions

One nice operations tool is a new jcmd command:

VM.security_properties

It lets admins inspect the active Java security properties of a running JVM. For example:

jcmd <pid> VM.security_properties

In situations where an application’s cryptocurrency or certificate behavior deviates from your expectations due to specific security property settings, understanding these discrepancies can be beneficial. Additionally, the jcmd tool now features Bash completion for Linux users, enhancing the efficiency of locating and entering diagnostic commands.

A few more quiet improvements are worth a mention:

  • Better reporting of open file descriptors in JVM diagnostics, which helps track down resource leaks.
  • JSON thread dumps now emit key identifiers and counts as JSON numbers, so monitoring tools parse them more easily.
  • Crypto performance gains for algorithms like SHA-3, ML-KEM, ML-DSA, X25519, and Ed25519.
  • More G1 tuning, compiler and Vector optimizations, and faster file and native I/O.

These rarely make headlines the way new syntax does. But they explain why upgrading Java can improve an app even when the source code stays the same. The JVM itself just keeps getting better.

11. Preview Features vs Incubator Features in Java 27

Before you use any of these in production, the status of each feature matters. Let us clear up the two labels you keep seeing.

11.1 What a Preview Feature Means

A preview feature is basically finished in design and implementation, but not yet permanent in Java SE. Java 27 has four of them:

  • JEP 538 for PEM encodings.
  • JEP 532 for primitive patterns.
  • JEP 531 for lazy constants.
  • JEP 533 for structured concurrency.

Preview language and API features usually need a flag during compile and run:

--enable-preview

This gives you a chance to try the feature and send feedback, while the JDK team can still tweak it before it becomes final.

11.2 What an Incubator Feature Means

The Vector API sits in a different bucket: incubator. Incubator modules hold APIs that are still shifting more heavily, kept apart in jdk.incubator.* modules. For the Vector API, you opt in with:

--add-modules jdk.incubator.vector

This split is worth keeping in mind when you design long-lived APIs. Playing with preview and incubating features is great. But building a public library whose permanent API leaks those types is risky, since the underlying API may change in a later release.

12. Should Existing Java Applications Upgrade to Java 27?

Java 27 is a non-LTS release. That matters for teams who standardize production on long-term-support versions.

The previous LTS release, Java 25, is still the natural base if your top priority is long-term stability. But that does not make Java 27 pointless for Java 25 teams. Far from it.

12.1 Why Non-LTS Still Deserves Attention

Short-term releases give you an early look at technology that may shape the next LTS. Runtime changes can also surface compatibility or performance surprises worth knowing before a big migration. A few areas are worth testing right away:

  • Apps in very small containers should check the impact of G1 now being the default instead of Serial GC.
  • Object-heavy apps should benchmark memory with compact object headers.
  • Teams using JFR in production should test the new redaction and confirm their secret-naming conventions are covered.
  • Security teams should understand the new post-quantum TLS negotiation.
  • Concurrency-heavy teams should try structured concurrency together with virtual threads.
  • Library and framework authors should keep exploring primitive patterns, lazy constants, and the Vector API.

Oracle notes that JDK 27 follows the usual six-month cadence, with the next feature release due to supersede it around March 2027. So for teams on Java 25 LTS, Java 27 works best as an evaluation and preparation release, even if production stays on an LTS JDK.

13. What Java 27 Tells Us About the Future of Java

Look at all nine JEPs together, and a clearer picture appears. Java is evolving at several layers at once, not just in syntax.

  • Language level: primitive patterns make the type system more consistent and expressive.
  • Concurrency level: structured concurrency suits the era of virtual threads.
  • Performance level: G1 everywhere and compact object headers help existing apps, mostly without code changes.
  • Hardware level: the Vector API bridges Java and SIMD-capable processors.
  • Startup level: lazy constants postpone work until it is truly needed.
  • Security level: TLS is preparing for post-quantum crypto, and PEM handling gets easier.
  • Operations level: JFR redaction and new diagnostics make production JVMs safer and easier to debug.

That combination is the real story. Modern Java is no longer growing only by adding language syntax. The platform is improving from the CPU instruction level, up through the JVM, security libraries, concurrency model, language, and production tooling.

14. FAQ’s on Java 27

Q: Is Java 27 an LTS release?

A: No. Java 27 is a non-LTS feature release, delivered on the usual six-month cadence. The previous LTS is Java 25, which stays the natural base for teams that need long-term stability. Oracle plans to supersede Java 27 with Java 28 around March 2027. For LTS teams, Java 27 is best used as an evaluation and preparation release.

Q: What are the main Java 27 features?

A: Java 27 ships nine JEPs: post-quantum hybrid key exchange for TLS 1.3, PEM encodings of cryptographic objects, primitive types in patterns, G1 as the default garbage collector everywhere, compact object headers by default, lazy constants, structured concurrency, the Vector API, and JFR in-process data redaction. Four are preview, one is incubator, and the rest are standard.

Q: Do I need to change my code to benefit from Java 27?

A: For many features, no. G1 becoming the default, compact object headers, post-quantum TLS, and JFR redaction all improve your app just by upgrading the JVM. Preview and incubator features like primitive patterns, lazy constants, structured concurrency, and the Vector API do require you to opt in with flags.

Q: How do compact object headers reduce memory in Java 27?

A: On 64-bit HotSpot, they cut the object header from 96 bits (12 bytes) to 64 bits (8 bytes), now on by default. For apps holding millions of objects, that adds up to real heap savings, plus better CPU cache use and deployment density. You can revert with -XX:-UseCompactObjectHeaders if testing shows a problem.

Q: What is the difference between a preview and an incubator feature in Java 27?

A: A preview feature is fully designed and implemented but not yet permanent; you enable it with –enable-preview. An incubator feature, like the Vector API, is still evolving more heavily and lives in a jdk.incubator.* module you enable with –add-modules. Both signal you should avoid baking their types into long-lived public APIs.

Q: How does structured concurrency work with virtual threads?

A: Virtual threads make it cheap to run many concurrent tasks; structured concurrency organizes related tasks as one unit of work inside a scope. Together, failure, cancellation, and cleanup happen as a group, and thread dumps show the parent-child structure. In Java 27 (seventh preview), standard joiners throw ExecutionException on failure.

15. Final Thoughts

Java 27 shows how Java has grown up as a platform. Older releases were often judged by shiny new syntax. Modern ones deserve a different lens.

Compact object headers can reduce memory usage for millions of objects without changing your code. Setting G1 as the default option won’t require any code changes, but it will provide more consistent behavior in different environments. Post-quantum TLS improves your network security without you needing to write any encryption code. JFR redaction helps keep sensitive information out of diagnostic files without requiring each operations team to create its own scrubbing tool.

At the same time, structured concurrency, lazy constants, primitive patterns, the PEM API, and the Vector API point to where the programming model is headed. So Java 27 feels less like a one-headline release and more like a careful strengthening of the whole platform.

For most enterprise developers, the features to understand first are G1 becoming universal, compact object headers, structured concurrency, post-quantum TLS, and lazy constants. But taken together, all nine JEPs tell one story. Java keeps evolving without losing what made it great: compatibility, portability, maintainability, performance, and a huge ecosystem.

Java 27 may not force you to rewrite your app. Instead, in several important ways, simply running on a newer JVM makes Java apps more efficient, more secure, and easier to operate. And that may be the most valuable Java 27 feature of all.

16. Further Reading

 

Leave a Comment