Java 27 GA 15 Sep 2026. Four production defaults moved. Previews (lazy constants, structured concurrency) stay in the lab. Flags, opt-outs, JEP links.
OpenJDK 27 went GA on 15 September 2026. JSR 402. I am writing this on the 21st. The mailing lists will spend the next month arguing about structured concurrency’s seventh preview. That is the wrong argument for a production image. I have been shipping Java for more than twenty-five years. My ATS API still lives in a 1-CPU, sub-2 GB container on a lot of environments. Before 27, that box sometimes woke up on the Serial collector because the JVM decided the environment was constrained. After 27, the same Dockerfile gets G1 unless I name another collector. Compact headers shrink every object. TLS 1.3 prefers a hybrid group. A Flight Recorder dump for a support ticket writes [REDACTED] over token-shaped keys. I did not enable a preview. I did not touch a servlet. You upgraded the JRE. You did not upgrade the language. That is the useful Java 27. This is not a tour of every incubator. It is the four defaults I will take on Monday, the flags that undo them, and the preview APIs I will keep compiling in a lab module. I am not selling a runtime. I am naming what already moved underneath a Spring Boot process I actually run. Java 27 is not a language drop. It is a default change. Nine JEPs, two piles The OpenJDK 27 project page lists nine JEPs. Four of them are product. Five of them still need --enable-preview or an incubator module. If a write-up mixes those piles, it is a changelog, not an operations note. Green rows ship with the JRE. Rose rows stay behind a preview flag I will not put on a recruiter-facing image. I will keep three labels. A shipped default is on unless you opt out. An opt-out is a flag the JEP documents, not a folklore JVM option. A preview is an API that can still rename a method between 27 and 28. Structured concurrency already did that in this cycle. That is why it is not in the ATS JAR. G1 is now everyone’s default JEP 523 is the quietest production change in the drop. Since JEP 248 in JDK 9, HotSpot picked Serial when it saw one CPU or less than 1792 MB of RAM. That was the “constrained environment” shortcut. Small Kubernetes requests, tiny local Compose services, and a lot of CI jobs lived on that path without anyone writing -XX:+UseSerialGC . 27 ends the shortcut. G1 is the default collector in every environment. Serial is not removed. If you still want it, you ask: -XX:+UseSerialGC . Same for Parallel or ZGC. The change is the silent pick, not the catalog. Same Dockerfile. Two runtimes, until 27. Print the flags before you argue about pause times. I will not invent a pause-time graph for my ATS. I will print what the process actually selected on the first 27 pod: Print the collector the image actually got java -XX:+PrintCommandLineFlags -version # Look for +UseG1GC on a 1-CPU, 1g box. # Serial is still legal. It is no longer the quiet default. # -XX:+UseSerialGC If a sidecar still assumes Serial’s single-threaded pauses, that sidecar was already lying on every 4-CPU replica. 27 just stopped protecting the 1-CPU replica from the same collector. That is a gift if your dashboards assumed G1. It is a surprise if they assumed Serial and you never noticed. 32 bits you did not ask for JEP 534 turns compact object headers on by default on 64-bit HotSpot. The old layout was a 64-bit mark word plus a 32-bit compressed class pointer: 96 bits before your fields started. The compact layout folds class bits into the mark word. 64 bits. JDK 25 already shipped the feature as a product flag, -XX:+UseCompactObjectHeaders , after production use at Amazon and SAP. 27 stops making you type it. Cite the JEP’s SPECjbb experiments. Do not paste them into Slack as if you ran them on the ATS. The JEP cites SPECjbb2015 experiments: around 22% less heap, 8% less CPU, 15% fewer GCs in one setting. Those are not my numbers. I will measure occupancy on my service after the bump. I will not treat a spec-jbb paragraph as a capacity plan. Opt out if a native agent or an old Serviceability Agent still assumes 96-bit headers: Only if a native tool still assumes 96-bit headers # Java 27 default is compact headers on. # Undo it only for a tool that cannot read the new layout. -XX:-UseCompactObjectHeaders If your heap is a sea of small objects, this is the change that will show up in GC logs before any language preview does. That is the point of calling 27 a default drop. TLS that assumes harvest-now JEP 527 adds hybrid key-exchange groups to TLS 1.3. The threat is not a quantum computer on your VPC this quarter. The threat is a packet capture stored today and decrypted when a cryptographically relevant quantum computer exists. Hybrid means ML-KEM plus a classical ECDHE group. If either algorithm fails later, the other still holds. This is not a pure ML-KEM-only handshake. Three groups ship. X25519MLKEM768 is first on the default named-group list. SecP256r1MLKEM768 and SecP384r1MLKEM1024 are there if a peer wants NIST curves. The rest of the default list is still classical: x25519, secp256r1, secp384r1, secp521r1, x448, and the ffdhe families. A load balancer that only speaks classical ECDHE still negotiates down. That is expected, not a bug. A javax.net.ssl client that does not pin groups gets the hybrid offer with no Java change. If you already froze jdk.tls.namedGroups , you froze yourself out of the default. Pin on purpose, or take the list the JEP shipped: Pin hybrid first only if you already pin groups SSLParameters params = sslContext.getDefaultSSLParameters(); params.setNamedGroups(new String[] { "X25519MLKEM768", "x25519", "secp256r1" }); engine.setSSLParameters(params); # Or the system property, same names: # -Djdk.tls.namedGroups=X25519MLKEM768,x25519,secp256r1 I am not claiming a handshake-millisecond number. Handshake size goes up. Intermediaries that inspect named groups need to know the new identifiers. If a peer cannot speak hybrid, TLS still walks the rest of the list. Do not disable the hybrid group because a blog called it experimental. In 27 it is the default first offer. JFR that stops leaking tokens JEP 536 is the default I would have wanted five years ago. Flight Recorder can record initial environment variables, system properties, and JVM information. Those events are how a support zip becomes a credential leak. 27 redacts in process. The replacement is the literal string [REDACTED] . It is on by default. A Flight Recorder dump that still contains a token is a leak, not a diagnostic. Default globs already match password-shaped and token-shaped names. Extend them. Do not start from empty unless you know why. + keeps the defaults and adds yours. none turns redaction off. @file is allowed so the glob list does not live on the command line. Keep redaction on for any dump that leaves the box -XX:StartFlightRecording=filename=app.jfr,duration=60s # Extend defaults, do not replace them: # -XX:FlightRecorderOptions=redact-key=+*SECRET* # Debug a name that stayed visible: # -Xlog:jfr+redact=debug # Do not do this for a ticket zip: # -XX:FlightRecorderOptions=redact-key=none Events in the JEP’s blast radius include jdk.InitialEnvironmentVariable , jdk.InitialSystemProperty , and jdk.JVMInformation . If you ship dumps to a vendor, 27 is the first JDK where I will not treat that zip as automatically toxic. I will still not put a keystore password in an environment variable. Redaction is a backstop, not a permission slip. Previews stay in the lab The other five JEPs are real work. They are not Monday’s runtime. Preview is not a softer default. It is an API that can still move, and in 27 it did. Ship the JVM. Leave the seventh preview in the lab. JEP 533 is structured concurrency’s seventh preview. The join exception grew a type parameter. awaitAll() left. onTimeout became timeout() . I like the model. I will not import an API that renamed itself in this cycle into a recruiter-facing service. That motion is the reason it stays in a lab module. JEP 532 is primitive type patterns, fifth preview, no change v...