Java 27 Released: G1 Default GC, Compact Object Headers, Post-Quantum TLS, JFR Redaction

Java 27 introduces four final features: G1 becomes the universal default garbage collector, compact object headers reduce memory overhead by 4 bytes per object, TLS 1.3 adds post-quantum hybrid key exchange by default, and JFR automatically redacts sensitive data like passwords and tokens before recording.

java1234
java1234
java1234
Java 27 Released: G1 Default GC, Compact Object Headers, Post-Quantum TLS, JFR Redaction

Java 27 reached General Availability on 2026-09-15, delivering 9 JEPs: 4 final features, 4 preview features, and 1 incubator feature. This marks the 18th feature release in the six-month cadence.

Four Final Features

G1 Becomes Default GC in All Environments

Since Java 9, G1 has been the default garbage collector for server-class machines. However, an exception remained: on single-CPU systems or machines with less than 1792 MB of memory, the JVM silently fell back to Serial GC. Java 27 removes this exception. When no GC flags are provided, HotSpot now unconditionally selects G1, aligning default behavior across small containers, local development machines, and Raspberry Pi-class devices with server environments.

If a workload genuinely benefits from Serial GC, it can still be enabled explicitly: java -XX:+UseSerialGC -jar app.jar Daily development typically requires no changes. The key consideration is that scripts previously relying on the "small memory auto-selects Serial" behavior for startup gains will now follow a different default path; latency and memory usage should be re-validated under load.

Compact Object Headers Enabled by Default

This change reduces the object header on 64-bit JVMs from 96 bits to 64 bits, saving 4 bytes per object. It progressed from an experimental feature in Java 24, to a manually enabled option in Java 25 ( -XX:+UseCompactObjectHeaders), to the default in Java 27. Applications with many small objects—cache entries, DTOs, tree nodes, message envelopes—will see reduced heap usage and improved cache locality without code changes.

Previous Java 25 activation (no longer needed in 27):

# Java 25 era syntax, not required in 27
java -XX:+UseCompactObjectHeaders -jar app.jar

After upgrading to Java 27, compare heap consumption for object-heavy services; the improvement often appears in memory metrics rather than business logic.

TLS 1.3 Adopts Post-Quantum Hybrid Key Exchange

While quantum computers have not yet broken current public-key cryptography, "harvest now, decrypt later" attacks are a real concern. Java 27 adds hybrid key exchange to TLS 1.3, combining classic elliptic-curve X25519 with the post-quantum algorithm ML-KEM (specifically the X25519MLKEM768 named group).

The feature works transparently with the standard javax.net.ssl API: both endpoints automatically negotiate the hybrid suite when supported, falling back to classic algorithms otherwise. No code changes are required for existing applications.

To inspect the enabled named groups programmatically:

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;

public class TlsNamedGroupsDemo {
    public static void main(String[] args) throws Exception {
        SSLParameters params = SSLContext.getDefault().getDefaultSSLParameters();
        String[] groups = params.getNamedGroups();

        // Java 27 defaults include X25519MLKEM768 in available groups
        if (groups == null) {
            System.out.println("Using JVM default named groups (including hybrid post-quantum suite)");
            return;
        }
        for (String group : groups) {
            System.out.println(group);
        }
    }
}

Only when the peer does not support the hybrid suite, or when explicitly disabling it, is the jdk.tls.namedGroups system property needed. Most web services gain a security upgrade automatically upon deployment.

JFR Redacts Sensitive Data Before Recording

When sharing JFR recordings for troubleshooting, startup parameters, environment variables, and system properties often contain passwords and tokens. Previously these were written verbatim into the recording file. Java 27 redacts sensitive data before it leaves the process. By default, keywords password, token, and secret are matched and replaced with [REDACTED].

Custom redaction rules can be added via FlightRecorderOptions:

# Default rules suffice; to additionally redact custom parameters:
java -XX:FlightRecorderOptions:'redact-argument=--db-url *;redact-key=+internal*key*' \
     -XX:StartFlightRecording:filename=dump.jfr \
     -jar app.jar

To disable redaction entirely (not recommended for production):

java -XX:FlightRecorderOptions:'redact-argument=none,redact-key=none' -jar app.jar

Production environments should keep the default enabled; JFR is meant for human analysis, not for leaking credentials.

Preview Features Worth Exploring Early

Preview features are complete in specification and implementation, awaiting one more round of community feedback. They require --enable-preview at both compile and runtime.

Primitive Types Finally Work in Pattern Matching

switch

and instanceof previously restricted primitive types like int and double. After five preview rounds, primitives can now appear in patterns directly, and the compiler rejects narrowing conversions that would lose precision.

Example demonstrating switch with int patterns and instanceof with record deconstruction guarding against precision loss:

public class PrimitivePatternDemo {

    /** Calculate discount by item count; default no longer repeats value extraction */
    static int discountPercent(int items) {
        return switch (items) {
            case 2 -> 5;
            case 3, 4 -> 10;
            case int n when n >= 5 -> 20;
            case int n -> 0; // covers all remaining int values, including negatives
        };
    }

    record JsonNumber(double value) {}

    /** Only numbers that convert to int without loss enter this branch */
    static String describeAge(JsonNumber number) {
        if (number instanceof JsonNumber(int age) && age >= 0) {
            return "Age: " + age;
        }
        return "Not a clean integer age";
    }

    public static void main(String[] args) {
        System.out.println(discountPercent(6));              // 20
        System.out.println(describeAge(new JsonNumber(30)));   // Age: 30
        System.out.println(describeAge(new JsonNumber(30.8))); // Not a clean integer age
    }
}

Previously, manual range checks and casts were required, risking silent truncation (e.g., 30.8 becoming 30). Now a non-matching value simply follows another branch, eliminating a class of bugs.

LazyConstant: Lazy Initialization Without Performance Sacrifice

final

fields must be assigned during construction, forcing loggers, connection pools, and configuration objects to initialize at startup. Hand-written double-checked locking is error-prone and prevents the JVM from treating the field as a true constant for folding optimizations. LazyConstant solves this: the value is computed once on first access, becomes immutable thereafter, and initialization is thread-safe.

Example deferring logger creation until first order submission:

import java.lang.LazyConstant;
import java.util.logging.Logger;

public class OrderController {

    // Startup only allocates an empty holder; real Logger created on first order
    private final LazyConstant<Logger> logger =
            LazyConstant.of(() -> Logger.getLogger(OrderController.class.getName()));

    public void submitOrder(String userId) {
        logger.get().info("Processing order for user: " + userId);
        // ... business logic
        logger.get().info("Order submitted");
    }

    public static void main(String[] args) {
        new OrderController().submitOrder("u-1001");
    }
}

Components themselves can be lazily loaded, cleaning up the startup path:

import java.lang.LazyConstant;

public class AppContext {

    static final LazyConstant<OrderController> ORDERS =
            LazyConstant.of(OrderController::new);

    public static OrderController orders() {
        return ORDERS.get();
    }
}

For cloud-native services, the value lies not in syntax elegance but in avoiding unnecessary initialization during cold starts.

Structured Concurrency: Child Tasks Stay Contained

Virtual threads make it cheap to fan out multiple I/O tasks per request, but ExecutorService leaves failure handling, cancellation, and thread leakage to the developer. Structured concurrency enforces a simple principle: child tasks must complete within the scope that created them .

A "fetch user + fetch order" operation using StructuredTaskScope:

import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
import java.util.concurrent.ExecutionException;

public class OrderHandler {

    record Response(String user, int orderId) {}

    Response handle() throws ExecutionException, InterruptedException {
        try (var scope = StructuredTaskScope.open()) {
            Subtask<String> user = scope.fork(this::findUser);
            Subtask<Integer> order = scope.fork(this::fetchOrder);

            scope.join(); // one failure cancels the other
            return new Response(user.get(), order.get());
        }
    }

    private String findUser() {
        return "alice";
    }

    private int fetchOrder() {
        return 2048;
    }
}

The execution flow can be understood as: the scope opens, both subtasks fork, join() waits for both; if one fails, the other is cancelled automatically; on success, results are retrieved via Subtask.get().

Structured concurrency execution flow diagram
Structured concurrency execution flow diagram

Now in its seventh preview, the API is still finalizing exception types. Production use should wait; new services using virtual threads for fan-out should experiment with scope in test environments first.

PEM Encoding/Decoding Without Manual String Manipulation

Certificates, public keys, and private keys on disk are almost always PEM text blocks ( -----BEGIN ...-----). Previously, developers had to Base64-encode/decode manually, detect types, and write dozens of lines for encrypted private keys. The third preview of the PEM API in Java 27 streamlines routine read/write:

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PEMDecoder;
import java.security.PEMEncoder;
import java.security.PrivateKey;
import java.security.PublicKey;

public class PemDemo {

    public static void main(String[] args) throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
        generator.initialize(256);
        KeyPair pair = generator.generateKeyPair();

        PEMEncoder encoder = PEMEncoder.of();
        String publicPem = encoder.encodeToString(pair.getPublic());
        String privatePem = encoder.withEncryption("123456".toCharArray())
                .encodeToString(pair.getPrivate());

        System.out.println(publicPem);

        PEMDecoder decoder = PEMDecoder.of();
        PublicKey publicKey = decoder.decode(publicPem, PublicKey.class);
        PrivateKey privateKey = decoder.withDecryption("123456".toCharArray())
                .decode(privatePem, PrivateKey.class);

        System.out.println("Public key algorithm: " + publicKey.getAlgorithm());
        System.out.println("Private key algorithm: " + privateKey.getAlgorithm());
    }
}

Teams interacting with OpenSSL, certificate authorities, or hardware security modules can retire custom utility classes.

Vector API remains in its 12th incubation round, primarily serving numerical computing and inference workloads. To use it, add --add-modules jdk.incubator.vector . It will likely graduate once Valhalla's value types mature; not covered further here.

Should You Upgrade Now?

Guidance depends on your current baseline:

Production on 17 / 21 : Stay on LTS; treat 27 as a research version

Already on 25 LTS : Test 27 in pre-production, focus regression on GC and TLS

New project, tight container memory : Good candidate for 27; compact object headers are free gains

Want pattern matching / structured concurrency : Enable preview in test; do not rush to production

Easiest way to try locally:

# Final features, run directly
java --version
java -jar app.jar

# Preview features
javac --release 27 --enable-preview PrimitivePatternDemo.java
java --enable-preview PrimitivePatternDemo

Oracle will provide updates for Java 27 until 2027-03, after which Java 28 takes over. Java 27 is not an LTS release; do not treat it as a "two-year production baseline."

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Structured ConcurrencyPattern MatchingG1 GCLazyConstantCompact Object HeadersJava 27Post-Quantum TLSJFR Redaction
java1234
Written by

java1234

Former senior programmer at a Fortune Global 500 company, dedicated to sharing Java expertise. Visit Feng's site: Java Knowledge Sharing, www.java1234.com

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.