Java 25 LTS arrives with stable Scoped Values and Module Import: what to review in your service
JDK 25 became LTS in September 2025 and moved out of preview features that change concurrency and import organization. Here's what's already worth migrating and what still calls for caution in production.

JDK 25 reached General Availability on September 16, 2025, and, as recorded by the JDK 25 Project on OpenJDK, will be an LTS release from most vendors. For anyone running a Java service inside a polyglot stack, LTS is the word that matters: it's the version worth investing migration effort in, because it will receive security patches for years. The previous one in that tier was JDK 21. So the practical question isn't "what's new," but "which of these 18 JEPs affects code that's already running."
It's worth separating the wheat from the chaff right away. Much of JDK 25's list is still preview, incubator, or experimental: Stable Values (Preview), Structured Concurrency (Fifth Preview), Primitive Types in Patterns (Third Preview), Vector API (Tenth Incubator), PEM Encodings (Preview). None of that should go into a production service without --enable-preview, and enabling preview in production is a decision that rarely pays off. What came out of preview and became a stable API in this cycle is what deserves immediate attention: Scoped Values (JEP 506) and Module Import Declarations (JEP 511).
Scoped Values: the serious replacement for ThreadLocal
Scoped Values arrived as a final feature in JDK 25 after several rounds of preview. The idea is to allow sharing immutable data from a method to the methods it calls, without passing the value as an argument through the entire chain, which is exactly the problem ThreadLocal always "solved" poorly.
The real pain point of ThreadLocal shows up with virtual threads (Project Loom). When you spawn millions of virtual threads, each carrying mutable copies of ThreadLocal, memory cost and lifecycle mess explode. ThreadLocal is mutable, inheritable in surprising ways, and depends on you remembering to call remove(). Scoped Values flip that around: the value is immutable and lives only within a delimited scope.
In practice, the format looks like this:
private static final ScopedValue<Usuario> USUARIO = ScopedValue.newInstance();
ScopedValue.where(USUARIO, usuarioAutenticado)
.run(() -> processarRequisicao());
// deep in the call stack:
Usuario u = USUARIO.get();Outside the run/call block, the value simply doesn't exist, there's no leak and no forgotten remove(). For code that today uses ThreadLocal to carry request context (authenticated user, tenant, trace ID), this is the most direct migration use case.
Where I'd hold back: Scoped Values being stable doesn't mean you should rewrite every ThreadLocal in the codebase in one sprint. The real gain shows up when there are virtual threads and high task fan-out. In a traditional platform-thread pool with a few hundred threads, the difference is marginal, and the risk of introducing a bug into a context layer that "already works" doesn't pay off. The migration is worth doing as part of adopting virtual threads, not in isolation from it. And it's worth remembering that Structured Concurrency is still in preview (fifth round) in 25, so the ideal pairing (Scoped Values + Structured Concurrency) isn't fully on stable ground yet.
Module Import Declarations: fewer imports, more readability
JEP 511 also came out of preview. It allows importing all packages exported by a module at once, with a single line:
import module java.base;This makes types like List, Map, Stream, Path available in one stroke, without the list of individual imports at the top of the file. It's syntactic sugar aimed mainly at people teaching Java, writing scripts, and prototyping, and it pairs with the compact source files JEP (JEP 512, also finalized in JDK 25) that tries to lower the language's entry barrier.
For a production service with a properly configured IDE, the impact is honestly small: your IDE already organizes imports automatically, and the explicitness of import java.util.List tends to be desirable in a large codebase, because it makes clear where each type comes from. It's not a feature that changes architecture. It can be useful in internal scripts, tooling, and lean test code, but I wouldn't swap the team's explicit import pattern for module import in a mature codebase just because it became stable. It's an ergonomics gain in a specific context, not a structural improvement.
What to review when upgrading to 25
Some items in the release affect runtime behavior and deserve a spot on the checklist even without touching application code:
- Remove the 32-bit x86 Port (JEP 503): if by any chance there's still a 32-bit x86 build somewhere in a legacy pipeline, it dies here. Check before updating the base image.
- Compact Object Headers (JEP 519): reduces object header size on HotSpot, with real potential memory savings in heaps with lots of small objects. It's the kind of change that can improve footprint at no code cost, so it's worth measuring before and after in a staging environment.
- Generational Shenandoah (JEP 521): the Shenandoah collector gains a generational mode. If your service already uses Shenandoah, there's GC tuning worth reevaluating.
- Ahead-of-Time (JEPs 514 and 515) and JFR improvements (509, 518, 520): affect startup and profiling. Nothing mandatory, but JFR's CPU-Time Profiling (experimental) and method timing/tracing are good for anyone investigating latency.
The migration path that makes sense
For a service already running on JDK 21 (the previous LTS), the jump to 25 tends to be relatively calm, because both are LTS and the range of incompatible changes is smaller than jumping from an intermediate version. The roadmap I'd propose: upgrade the runtime version first, run the test suite, and watch memory footprint (because of Compact Object Headers) and GC behavior, without touching application code. Only afterward, in a second stage and if virtual thread adoption is on the radar, evaluate migrating ThreadLocal to Scoped Values where the per-request context pattern actually exists.
Module Import Declarations and the compact source file JEPs fall into the "good to know it exists" category, useful for onboarding and scripts, dispensable in the main codebase. And anything marked preview, incubator, or experimental stays out of production until it stabilizes, no matter how tempting Structured Concurrency looks. The rule as always: on an LTS, you migrate for stability and long-term support, and you adopt a new feature for the concrete problem it solves in your service, not for the novelty itself.
Source 1: OpenJDK — JDK 25 Project (official JEPs) (https://openjdk.org/projects/jdk/25/)
JDK 25
JDK 25 This release is the Reference Implementation of version 25 of the Java SE Platform, as specified by JSR 400 in the Java Community Process. JDK 25 reached General Availability on 16 September 2025. Production-ready binaries under the GPL are available from Oracle ; binaries from other vendors will follow shortly. The features and schedule of this release were proposed and tracked via the JEP Process , as amended by the JEP 2.0 proposal . The release was produced using the JDK Release Process (JEP 3) . Features
470: PEM Encodings of Cryptographic Objects (Preview) 502: Stable Values (Preview) 503: Remove the 32-bit x86 Port 505: Structured Concurrency (Fifth Preview) 506: Scoped Values 507: Primitive Types in Patterns, instanceof, and switch (Third Preview) 508: Vector API (Tenth Incubator) 509: JFR CPU-Time Profiling (Experimental) 510: Key Derivation Function API 511: Module Import Declarations 512: Compact Source Files and Instance Main Methods 513: Flexible Constructor Bodies 514: Ahead-of-Time Command-Line Ergonomics 515: Ahead-of-Time Method Profiling 518: JFR Cooperative Sampling 519: Compact Object Headers 520: JFR Method Timing & Tracing 521: Generational Shenandoah JDK 25 will be a long-term support (LTS) release from most vendors. For a complete list of the JEPs integrated since the previous LTS release, JDK 21, please see here . Schedule
2025/06/05 Rampdown Phase One (branch from main line) 2025/07/17 Rampdown Phase Two 2025/08/07 Initial Release Candidate 2025/08/21 Final Release Candidate 2025/09/16 General Availability Last update: 2025/9/23 17:40 UTC Installing Contributing Sponsoring Developers' Guide Vulnerabilities JDK GA/EA Builds Mailing lists Wiki · IRC Mastodon Bluesky Bylaws · Census Legal · AI Workshop JEP Process Source code GitHub Mercurial Tools Git jtreg harness Groups (overview , archive ) Adoption Build Client Libraries Compatibility & Specification Review Compiler Conformance Core Libraries Governing Board HotSpot IDE Tooling & Support Internationalization Members Networking Porters Quality Security Serviceability Vulnerability Web Projects ( overview , archive ) Amber Babylon Brisbane CRaC Code Tools Coin Common VM Interface Detroit Developers' Guide Duke IcedTea JDK 8 Updates JDK 9 JDK (…, 26 , 27 , 28 ) JDK Updates JMC Jigsaw Lanai Leyden Lilliput Loom Memory Model Update Multi-Language VM Nashorn New I/O OpenJFX Panama Port: AArch32 Port: AArch64 Port: BSD Port: Haiku Port: MIPS Port: Mobile Port: PowerPC/AIX Port: RISC-V Port: s390x SCTP Shenandoah Skara Sumatra Tsan Valhalla Wakefield Zero ZGC
© 2026 Oracle Corporation and/or its affiliates
Terms of Use ·
License: GPLv2 · Privacy · Trademarks
Translated from the Brazilian Portuguese original · Read the original
CodeQL 2.27.1 gets C/C++ queries and Kotlin 2.4.20 support
The version released on September 25, 2026 refines GitHub's static analysis engine with new taint flow models for C/C++, adjustments to Kotlin's K2 compiler, and fixes that reduce false positives across several languages.
