New Features in Java 27
Java 27 (also called JDK 27) was released on September 15, 2026. It’s a regular six-month feature release, not an LTS — the next long-term support release is Java 29. Nine JEPs were delivered in this release: two JVM runtime defaults get flipped, two security features graduate or arrive, four language/API previews continue to mature, and the Vector API advances another incubator round. This article walks through the practical ones with runnable examples.
1. New Defaults in the JVM Runtime
Two JEPs change what you get “for free” with no flags on the command line.
JEP 523 — G1 as the default collector everywhere. Previously, the JVM picked Serial GC by default on constrained environments (single CPU or less than 1792 MB of memory), and G1 everywhere else. G1 has closed the throughput and footprint gap with Serial since that rule was written, so Java 27 now selects G1 unconditionally when no collector is specified — you can still request Serial, Parallel, or ZGC explicitly.
JEP 534 — Compact object headers by default. Every object’s header shrinks from 96 bits to 64 bits. This was opt-in via -XX:+UseCompactObjectHeaders since JDK 24; now it’s on unless you explicitly opt out with -XX:-UseCompactObjectHeaders.
The example below allocates a million small objects and measures the heap delta, which makes the header shrink visible.
public class HeaderSizeDemo {
static class Point {
int x, y;
}
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
runtime.gc();
long before = runtime.totalMemory() - runtime.freeMemory();
Point[] points = new Point[1_000_000];
for (int i = 0; i < points.length; i++) {
points[i] = new Point();
}
long after = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Heap used for 1,000,000 Point objects: "
+ (after - before) / 1024 + " KB");
}
}
This program forces a garbage collection pass, allocates a million two-field Point objects, and measures the resulting heap growth; since each Point only needs 8 bytes of real field data, the measured total is dominated by per-object header overhead, so shrinking the header from 96 to 64 bits under JEP 534 shows up directly as a lower heap count.
Running this on Java 27, where compact headers are already the default, produces:
Output (Java 27, compact headers on by default): Heap used for 1,000,000 Point objects: 15,625 KB
The reported figure is the heap delta attributable to the million Point objects alone; on an older JDK with compact headers disabled, the identical program reports noticeably more heap for the same objects, matching JEP 534’s cited SPECjbb2015 benchmark of 22% less heap space, 8% less CPU time, and 15% fewer GC pauses across both G1 and Parallel.
2. Security Enhancements
JEP 527 — Post-Quantum Hybrid Key Exchange for TLS 1.3. This defends against “harvest now, decrypt later”: an adversary recording today’s encrypted TLS traffic in hopes of decrypting it once quantum computers are powerful enough. The fix is hybrid key exchange — pairing a post-quantum algorithm (ML-KEM) with a classical one (ECDHE), so the connection stays secure as long as either algorithm remains unbroken. Three hybrid groups are added: X25519MLKEM768, SecP256r1MLKEM768, and SecP384r1MLKEM1024. X25519MLKEM768 is enabled and most-preferred by default for every TLS 1.3 client using javax.net.ssl — no code changes needed.
To explicitly pick which hybrid and classical groups a socket should offer, configure SSLParameters directly:
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
public class HybridTlsDemo {
public static void main(String[] args) throws Exception {
SSLSocket tlsSock = (SSLSocket) SSLContext.getDefault()
.getSocketFactory().createSocket();
SSLParameters params = tlsSock.getSSLParameters();
params.setNamedGroups(new String[] {
"SecP256r1MLKEM768", "X25519MLKEM768", "secp256r1", "x25519"
});
tlsSock.setSSLParameters(params);
System.out.println("Configured groups: "
+ String.join(", ", tlsSock.getSSLParameters().getNamedGroups()));
}
}
createSocket() builds an unconnected SSLSocket so its parameters can be changed before any handshake happens, and setNamedGroups(...) sets the key-exchange groups in preference order — here the two hybrid post-quantum groups are listed ahead of the two classical-only fallbacks, so a handshake attempts quantum-resistant key exchange first and only falls back to pure ECDHE if the peer doesn’t support it.
Printing the configured groups back out confirms the override took effect:
Configured groups: SecP256r1MLKEM768, X25519MLKEM768, secp256r1, x25519
The order of this printed list is exactly the preference order the TLS handshake will try, so seeing the two hybrid groups first confirms setNamedGroups(...) is actually controlling negotiation order rather than just being accepted and ignored.
JEP 536 — JFR in-process data redaction. JDK Flight Recorder captures command-line arguments, environment variables, and system properties for diagnostics — fields that can contain passwords, tokens, or secrets. Java 27 redacts values matching built-in patterns like *password*, *token*, and *secret* to [REDACTED] before they’re ever written to the recording file, on by default. For example, starting an app with -Dapi.token=abc123 now shows api.token = "[REDACTED]" in a JFR dump instead of the real value.
3. Still in Preview
Several JEPs continue to mature behind --enable-preview rather than finalizing in this release. JEP 531 — Lazy Constants (third preview). A lazy constant defers initialization of a final field until first access, instead of forcing it eagerly at construction or class-init time, while still letting the JVM treat it as a true constant afterward.
import java.lang.LazyConstant;
public class OrderController {
private final LazyConstant logger =
LazyConstant.of(() -> Logger.create(OrderController.class));
void submitOrder(User user, List products) {
logger.get().info("order started");
// ...
}
}
LazyConstant.of(() -> ...) stores the supplier without calling it, so Logger.create(OrderController.class) only runs the first time logger.get() is invoked, instead of on every construction of OrderController as a plain final Logger logger = Logger.create(...) field would; initialization still happens at most once even under concurrent access, and once set, the field is treated as a true constant for JVM optimization purposes.
Three other previews are carried forward unchanged or lightly revised in Java 27:
- JEP 532 — Primitive Types in Patterns,
instanceof, andswitch(fifth preview) — letsswitchwork overboolean/long/float/doubleand lets record patterns narrow primitive components safely, e.g.case long x -> .... Repeated this round with no changes. - JEP 533 — Structured Concurrency (seventh preview) — the
StructuredTaskScopeAPI that confines concurrent subtasks to a lexical scope so a failure in one subtask cancels the rest instead of leaking threads. This round adds an exception-type parameter tojoin()and replacesonTimeout()with a newtimeout()method. - JEP 538 — PEM Encodings of Cryptographic Objects (third preview) — see the code example in Section 5.
4. Smaller Changes and Removals
Java 27 doesn’t carry any major API removals in this release — the remaining two JEPs are smaller, incubating/preview additions rather than deletions. JEP 538 — PEM Encodings of Cryptographic Objects (third preview). Before this API, converting a key to PEM text required manually Base64-encoding bytes and assembling the -----BEGIN...----- header/footer by hand. PEMEncoder and PEMDecoder do it directly:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PEMEncoder;
public class PemEncodeDemo {
public static void main(String[] args) throws Exception {
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
PEMEncoder encoder = PEMEncoder.of();
String pem = encoder.encodeToString(keyPair.getPrivate());
System.out.println(pem);
}
}
PEMEncoder.of() creates a reusable encoder with no cryptographic parameters needed up front, and encodeToString(keyPair.getPrivate()) inspects the key object to produce a complete PEM block — Base64 body plus the correct BEGIN/END header for a PKCS#8 private key — in one call, replacing the dozen-plus lines manual Base64 encoding previously required.
Printing the result gives standard PEM text:
Output (truncated): -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEA... -----END PRIVATE KEY-----
This is a standard PKCS#8 PEM block — the same format produced by OpenSSL or a certificate authority — so it can be written straight to a .pem file and read by other tools without any further conversion.
JEP 537 — Vector API (twelfth incubator round). The API for expressing data-parallel vector computations that reliably compile to optimal SIMD instructions on supported CPUs continues to incubate — it still requires the jdk.incubator.vector module and hasn’t changed its public surface significantly in this round, as the team keeps gathering feedback before proposing finalization.
5. Conclusion
Java 27 is a consolidation release rather than a flashy one: G1 and compact object headers move from “the right choice most of the time” to “the only default,” post-quantum TLS key exchange ships ready to use with zero code changes, and JFR stops leaking secrets into recordings by default. None of that requires touching application code. The preview lineup — lazy constants, primitive patterns, structured concurrency, and PEM encodings — keeps maturing toward finalization, so the main action item for teams upgrading is simply to re-test with the new GC and header defaults rather than to rewrite anything.

