Java 25 New Features With Examples Core Java java Java 25 by devs5003 - September 25, 2025January 27, 20260 Last Updated on January 27th, 2026 Java 25 New Features With Examples Java 25 was officially released on September 16, 2025. It is a Long-Term Support (LTS) release that includes numerous enhancements across core Java libraries, language specifications, security, and performance. Oracle plans to provide support for Java 25 for at least eight years, allowing organizations to migrate at their own pace while benefiting from the latest features, including improved AI capabilities and enhanced developer productivity. Java 25, the latest Long-Term Support (LTS) release offers major upgrades across language syntax, APIs, security, performance, and monitoring, making Java easier and more powerful for everyone. All new features are introduced via JDK Enhancement Proposals (JEPs), so each section lists the JEP, summarizes the upgrade, and provides practical code samples or use cases. Table of Contents Toggle JEP 507: Primitive Types in Patterns, instanceof and switch (Third Preview)JEP 512: Compact Source Files and Instance Main MethodsJEP 513: Flexible Constructor BodiesJEP 511: Module Import DeclarationsJEP 505: Structured Concurrency (Fifth Preview)What is Structured Concurrency?Why Use Structured Concurrency?Progress of Structured Concurrency through several JDK releasesExample:JEP 506: Scoped ValuesWhat Are Scoped Values?Why Not ThreadLocal?How to Use Scoped Values?Example: Context Propagation in a Web FrameworkExample Demonstrating the Change in ScopedValue.orElse() methodJEP 502: Stable Values (Preview)Why Stable Values?Key APIExample:Comparison with Lazy InitializationAdvanced Example: Startup OptimizationComparision Summary TableBenefits and Best PracticesJEP 510: Key Derivation Function APIWhat is a Key Derivation Function (KDF)?Key Classes and MethodsExample:JEP 470: PEM Encodings of Cryptographic Objects (Preview)What is PEM?JEP 508: Vector API (Tenth Incubator)JEP 519: Compact Object HeadersJEP 521: Generational ShenandoahJEP 514: Ahead-of-Time Command-Line ErgonomicsJEP 515: Ahead-of-Time Method ProfilingJEP 503: Remove the 32-bit x86 PortWhat Was Removed?Platforms Still SupportedImpact on DevelopersJEP 509: JFR CPU-Time ProfilingJEP 518: JFR Cooperative SamplingJEP 520: JFR Method Timing & TracingComplete Table of JEPs in Java 25 New Features With ExamplesConclusionRelated JEP 507: Primitive Types in Patterns, instanceof and switch (Third Preview) The goal of this feature is to enable uniform data exploration by allowing type patterns for all types, whether primitive or reference. This feature was originally proposed by JEP 455 (JDK 23) and re-previewed by JEP 488 (JDK 24), without change. In JDK 25, it is proposed as a preview feature for a third time, without change. Java’s pattern matching now supports primitive types, streamlining code and reducing errors. This enables direct, type-safe matching and destructuring of primitives without unnecessary boxing or verbose code. Pattern cases for primitives simplify switch and instanceof. This feature promotes pattern matching for primitives more expressively: Example#1: Object obj = 42; switch (obj) { case int i -> System.out.println("Primitive int: " + i); case double d -> System.out.println("Primitive double: " + d); default -> System.out.println("Something else"); } Example#2: Integer code=-1; switch (code) { case int n when n > 0 -> System.out.println("Positive int: " + n); case int n when n < 0 -> System.out.println("Negative int: " + n); default -> System.out.println("Zero"); } This avoids unnecessary casting, boxing/unboxing and makes the code much cleaner. Example#3: Object obj = 10; if (obj instanceof int val) { System.out.println("Primitive int value: " + val); } else if (obj instanceof double val) { System.out.println("Primitive double value: " + val); } Here, Primitive pattern matching allows instanceof to be used not just for reference types, but also primitive types like int, double, etc. It removes the need for manual unboxing and type-casting boilerplate. In previous Java versions, developers would have to write explicit casting logic. If obj is compatible (e.g., a boxed Integer for int, or a Double for double), the match succeeds, and the primitive variable is directly usable. This approach improves type safety and readability when working with generically-typed or boxed values. JEP 512: Compact Source Files and Instance Main Methods This feature was first previewed in Java SE 21 as JEP 445: Unnamed Classes and Instance Main Methods (Preview) and previewed again in Java SE 22, 23, and 24, this feature is permanent in this release with a revised title ‘Compact Source Files and Instance Main Methods‘. Following are the updates in this release: A key change involves the package location of the basic console I/O class, IO, which is now part of the java.lang package. This placement means it is implicitly accessible to every Java source file. For compact source files, the static methods from the IO class are no longer implicitly imported. Method calls must now be prefixed with the class name, as in IO.println(“Hello, world!”), unless a specific static import statement is used. The implementation of the IO class has been refactored; it now functions as a wrapper for System.out and System.in, replacing its previous dependency on the java.io.Console class. You may check the status of this feature (JEP-495) in Java 24 release. JEP 513: Flexible Constructor Bodies The feature was first previewed in Java SE 22 as JEP 447: Statements before super(…) (Preview), next previewed again in Java SE 23 and Java SE 24. Now this feature is permanent in this release without any significant changes. This feature allows statements to run in the prologue, before the explicit constructor invocation (super(…) or this(…)). You can now validate inputs, perform calculations, or invoke helper methods; all before calling the superclass constructor. However, you still cannot reference the object under construction (no this, no accessing fields that aren’t yet initialized) in this context. Key new capabilities: Input validation before superclass construction. Calculations or preparation of arguments passed to the superclass. Cleaner, safer object construction, fail fast before expensive allocation. Restrictions You cannot read fields or call instance methods on this before constructor chaining. Fields can be initialized, but you must not leak references to the partially constructed object. Most static methods and local helpers are permitted in this pre-super region. Example#1: Defensive Programming (Argument Validation): public class PositiveBigInteger extends BigInteger { public PositiveBigInteger(long value) { if (value <= 0) throw new IllegalArgumentException("Value must be positive"); super(Long.toString(value)); } } Here, validation prevents passing bad values to the superclass. Example#2: Conditional Super Constructor Calls: public class CustomLogger extends Logger { public CustomLogger(String config) { String level = "INFO"; if ("debug".equals(config)) { level = "DEBUG"; } super(level); // can compute before super() } } Arguments are computed before superclass allocation. Example#3: Helper Method Invocation: public class Metric extends DataPoint { public Metric(String key) { String normalizedKey = normalizeKey(key); super(normalizedKey); } private static String normalizeKey(String key) { return key.trim().toLowerCase(); } } Helpers can be run before superclass constructor. Example#4: Record Constructors and JEP 513: public record ValidatedPoint(int x, int y) { public ValidatedPoint(int x, int y) { checkNonNegative(x, y); // now allowed before this() this(x, y); } private static void checkNonNegative(int x, int y) { if (x < 0 || y < 0) throw new IllegalArgumentException(); } } Non-canonical record constructors benefit from this flexibility. Example#5: Enum Constructors: public enum Status { ACTIVE("A"), INACTIVE("I"); private final String code; Status(String code) { if (!code.matches("[AI]")) throw new IllegalArgumentException(); this.code = code; } } Validation can now happen before field initialization. JEP 511: Module Import Declarations This feature was first previewed in Java SE 23, and previewed again in Java SE 24. It is now proposed to finalize in this release without any significant changes. This feature introduces Module Import Declarations to the Java language, allowing developers to import all public types from all exported packages in a module with a single statement: import module <ModuleName>; With import module, bring all exported classes from a module in one step. The introduction of module import declarations aims to simplify software development in several key ways. 1) Streamline Library Usage: Enable the import of all types from a module with a single declaration, simplifying the use of modular libraries. 2) Reduce Import Redundancy: This reduces clutter by replacing multiple wildcard package imports with a single, concise module import declaration. 3) Lower the Learning Curve: Make it easier for newcomers to utilize essential Java classes and third-party libraries without first needing to know their specific package locations. 4) Maintain Compatibility: Ensure that new module import declarations can coexist without conflict alongside traditional package import statements. 5) Support Incremental Adoption: Allow developers to benefit from module imports even if their own codebase is not yet fully modularized. Examples and further explanations of this feature is already discussed in detail in Module Import Declarations (JEP-494, Second Preview) of Java 24. Below artifacts are discussed in Module Import Declarations- Java 24: How it works? How to handle ambiguity while using Module import statements? Transitive dependency in Java Module System Implicit Availability of java.base Explicitly Requiring java.se JEP 505: Structured Concurrency (Fifth Preview) What is Structured Concurrency? Structured concurrency is a paradigm that treats related concurrent tasks as a single unit of work, with a well-defined dynamic scope. The principal API in Java is StructuredTaskScope, which ensures that: The lifetime of subtasks is bounded by the lexical scope of the block (usually a try-with-resources statement). Error handling, result collection, and cancellation are all coordinated in a clear way. Observability and propagation of interruption are improved. This contrasts with “unstructured” thread management (e.g., ExecutorService, CompletableFuture), where task lifecycles are often harder to track and errors/cancellations can silently propagate or be lost. Why Use Structured Concurrency? Simpler error handling: If any subtask fails, the rest are automatically canceled. Clear lifecycle: All subtasks begin and end inside a single code block, preventing thread/resource leaks. Coordinated cancellation: Parent scope interruptions propagate to all child tasks. Clean composition: Results from all tasks are available or a collective failure is raised. Progress of Structured Concurrency through several JDK releases The development of Structured Concurrency has progressed through several JDK releases: It started as an incubating feature in JDK 19 and JDK 20. It moved to preview in JDK 21, where the fork method’s return type was changed from Future to Subtask. It remained in preview through JDK 22, 23, and 24. In the JDK 25 release, proposed as another preview with key API improvements. Most notably, StructuredTaskScope objects will now be created using static factory methods. The basic zero-parameter open() factory method serves the common use case of waiting for all subtasks to complete or for one to fail. For more complex policies, developers can use other factory methods that accept a Joiner to customize how results are handled. Example: import java.util.concurrent.StructuredTaskScope; public Response handle() throws InterruptedException { try (var scope = StructuredTaskScope.open()) { var user = scope.fork(() -> findUser()); var order = scope.fork(() -> fetchOrder()); scope.join(); // Waits for both, propagates exceptions // Both succeeded return new Response(user.get(), order.get()); } } If either findUser() or fetchOrder() fails (throws), the other task is canceled, and an error is propagated. All resources are guaranteed cleaned up upon leaving the try block. Structured concurrency with JEP 505 enables safe, readable, and maintainable parallel code in Java by grouping subtasks into clear lifecycles and propagating errors/cancellations consistently. The StructuredTaskScope API is the core tool for building these patterns and makes Java concurrency code simpler and less error-prone. JEP 506: Scoped Values The scoped values API was proposed for incubation by JEP 429 (JDK 20), proposed for preview by JEP 446 (JDK 21), and subsequently improved and refined by JEP 464 (JDK 22), JEP 481 (JDK 23), and JEP 487 (JDK 24). Now, it is proposed to finalize the scoped values API in JDK 25, with one small change: The ScopedValue.orElse method no longer accepts null as its argument. What Are Scoped Values? Scoped Values are a new concurrency primitive that allows code to share immutable context data between callers, callees, and child threads, within a well-defined lexical scope. Unlike ThreadLocal, their lifetime, accessibility, and mutation are all strictly bounded and managed by the language runtime. Why Not ThreadLocal? ThreadLocal lets you attach mutable context to a thread, but the context’s lifetime is unclear, risking memory leaks or stale data. ThreadLocal values persist for the entire thread life unless explicitly removed, making them hazardous in environments using virtual threads, thread pools, or structured concurrency. Scoped Values fix these issues: Sessions/context are automatically inherited by child threads in a block. No manual removal or cleanup needed. The value is accessible only inside the bound scope. Values are truly immutable, preventing accidental sharing across requests. How to Use Scoped Values? Declaration: Scoped Values are typically declared as static final (like ThreadLocal), but they can only be read or bound inside a specific scope. private static final ScopedValue<UserContext> CTX = ScopedValue.newInstance(); Binding a Value to a Scope: Use the ScopedValue.where() method and run your logic within a lambda. ScopedValue.where(CTX, userContext) .run(() -> { processRequest(); }); Any code in processRequest including child threads or methods can access CTX.get() for that scope. Outside the scope, CTX.get() throws. Example: Context Propagation in a Web Framework Before scoped values (using ThreadLocal): private static final ThreadLocal<FrameworkContext> CONTEXT = new ThreadLocal<>(); void serve(Request request, Response response) { CONTEXT.set(createContext(request)); Application.handle(request, response); CONTEXT.remove(); } Risks include forgotten cleanup, leaking context, and confusing lifetimes. With Scoped Values: private static final ScopedValue<FrameworkContext> CONTEXT = ScopedValue.newInstance(); void serve(Request request, Response response) { var context = createContext(request); ScopedValue.where(CONTEXT, context).run(() -> { Application.handle(request, response); }); } public PersistedObject readKey(String key) { var context = CONTEXT.get(); // Read within scope var db = getDBConnection(context); return db.readKey(key); } The value bound in serve() is only readable inside the .run() block and any method invoked within that call stack. If readKey() is called after .run() returns, CONTEXT.get() will throw. No need to remove the value manually. Example Demonstrating the Change in ScopedValue.orElse() method In Java 25, the ScopedValue.orElse method was finalized with a key change: it no longer allows null as its argument. If you attempt to pass null to orElse, a NullPointerException will be thrown. The fallback value must always be non-null. Example Throwing NullPointerException import java.lang.ScopedValue; public class ScopedValueDemo { private static final ScopedValue<String> SCOPE = ScopedValue.newInstance(); public static void main(String[] args) { // No binding for SCOPE in this thread // This would previously be legal in preview, but now will throw NullPointerException String fallback = null; String value = null; try { value = SCOPE.orElse(fallback); // Throws NullPointerException in Java 25 } catch (NullPointerException e) { System.out.println("Caught expected NullPointerException: fallback cannot be null."); } // Correct usage: always supply a non-null fallback String safeValue = SCOPE.orElse("Default"); System.out.println("Safe fallback: " + safeValue); } } The first call to SCOPE.orElse(null) will throw a NullPointerException in Java 25. The correct usage is to always supply a non-null fallback, as in SCOPE.orElse(“Default”). This API change ensures safety and consistency, so you should always provide a non-null fallback when using ScopedValue.orElse in Java 25. passed, it will throw a NullPointerException instead of returning null. Correct Example: private static final ScopedValue<String> SCOPED_USER = ScopedValue.newInstance(); public static void main(String[] args) { // No binding exists for SCOPED_USER here String result = SCOPED_USER.orElse("guest"); // "guest" is valid fallback System.out.println(result); // Prints: guest } JEP 502: Stable Values (Preview) This JEP proposes a preview API for “stable values.” Stable Values are a Java 25 core-libs feature providing a new API for deferred immutability. What it is: A new API for creating “stable values”, objects with data that does not change. Performance: The JVM optimizes them just like final fields because their values are constant. Benefit: They offer more flexibility than final fields in how and when you assign their value. Purpose: To improve Java application startup performance by breaking down the large, single-block initialization of an app’s data into smaller, more efficient phases. Status: This is a preview API in this release. Why Stable Values? Traditional final fields must be initialized eagerly (during construction or static initialization), which can slow down startup for large applications. Mutable fields allow lazy initialization but lack JVM optimizations, and can lead to bugs from multiple or unsafe assignments. Stable Values solve both issues: you can set the value later, only once, and still benefit from constant-folding and thread-safety. Key API StableValue<T> is the primary class. StableValue.of() creates a new empty stable value. .orElseSet(Supplier<T>) atomically sets the value if absent (lazily), guaranteeing that only one thread succeeds. Example: import java.lang.StableValue; public class OrderController { // Deferred, immutable Logger—thread-safe, only set once! private final StableValue<Logger> logger = StableValue.of(); public Logger getLogger() { // If not already set, safely initialize it (lazily) return logger.orElseSet(() -> Logger.create(OrderController.class)); } } First call sets the value using the supplier, future calls return the immutable Logger. Comparison with Lazy Initialization Old way (before Stable Value): private Logger logger = null; Logger getLogger() { if (logger == null) { logger = Logger.create(OrderController.class); } return logger; } Not thread-safe, logger can be reassigned, no JVM constant-folding. With Stable Values (Java 25): private final StableValue<Logger> logger = StableValue.of(); Logger getLogger() { return logger.orElseSet(() -> Logger.create(OrderController.class)); } Thread-safe, only assigned once, JVM treats as constant after initialization. Advanced Example: Startup Optimization Suppose you have several expensive controllers in your app: public class Application { static final StableValue<OrderController> orders = StableValue.of(); static final StableValue<UserService> users = StableValue.of(); static OrderController getOrderController() { return orders.orElseSet(OrderController::new); } } The OrderController (and UserService) is constructed only when needed. No delay on startup, complete safety for concurrent access; once created, it’s immutable. Comparision Summary Table Feature final fields Stable Values (Java 25) Eager initialization Required Optional (deferred/lazy) Immutable reference Yes Yes (once set) JVM optimizations Yes Yes (after assignment) Thread safety Yes Yes (atomic, at-most-once) Startup delay Possible None (deferred) Preview status N/A Yes Benefits and Best Practices Performance: Use deferred initialization to improve application startup, no need to build all objects at once. Safety: StableValue guarantees at-most-once assignment, even across concurrent threads. Clarity: API makes it clear which fields are immutable but lazily initialized. Interoperability: JVM applies constant-folding optimizations for runtime usage, as it does for finals. JEP 510: Key Derivation Function API This introduces a standard API for Key Derivation Functions (KDFs). These are cryptographic tools used to generate new, secure keys from an existing secret key and additional data. This KDF API was first introduced as a preview feature in JDK 24 (via JEP 478). The API is now proposed for finalization as a permanent feature in JDK 25, with no modifications from its preview version. What is a Key Derivation Function (KDF)? A Key Derivation Function is a cryptographic primitive that derives one or more secret keys from a primary secret (such as a password, master key, or shared secret). KDFs are essential in protocols like TLS, secure password storage, and post-quantum cryptography schemes. They take inputs such as initial key material, salt, and optional context info to generate cryptographically strong keys. Key Classes and Methods javax.crypto.KDF: The main class representing a KDF algorithm. KDF.getInstance(String algorithm): Instantiates a KDF for a particular algorithm, e.g., “HKDF-SHA256”. KDF.deriveKey(String algorithm, AlgorithmParameterSpec spec): Derives a secret key for the specified algorithm. KDF.deriveData(AlgorithmParameterSpec spec): Derives byte array data (entropy, key material). Example: KeyDerivationFunction kdf = KeyDerivationFunction.create("HKDF"); SecretKey derived = kdf.deriveKey(originalKey, salt, info); JEP 470: PEM Encodings of Cryptographic Objects (Preview) This proposes a new preview API for working with the Privacy-Enhanced Mail (PEM) format. It will allow developers to encode security objects (like keys and certificates) into PEM text, and to decode PEM text back into their corresponding Java objects. Primary purpose of this feature is to create a simple and concise API to handle the translation between PEM-encoded data and the objects they represent, and making the process much easier for developers. What is PEM? Privacy-Enhanced Mail (PEM) is a widely used text-based format for encoding cryptographic objects such as certificates, public keys, and private keys. PEM files are Base64-encoded binary data wrapped with human-readable header and footer lines like: -----BEGIN CERTIFICATE----- ...Base64 encoded data... -----END CERTIFICATE----- PEM is the standard format used extensively in TLS/SSL certificates, SSH keys, and many security tools. JEP 508: Vector API (Tenth Incubator) JEP 508’s Vector API in Java 25 (tenth incubator) is an evolving and powerful API that lets Java developers write high-performance, portable, and readable vectorized code leveraging SIMD hardware capabilities, improving computational speed in many domains. This results in significantly better performance compared to traditional scalar (non-vector) code. The Vector API has been developed as an incubating feature over many JDK releases, starting with JDK 16. It has been updated in each subsequent release through JDK 24. It is now being proposed to continue its incubation in JDK 25 with several key improvements: API Enhancement: VectorShuffle can now work directly with data in off-heap memory (MemorySegment). Improved Maintainability: The internal implementation now uses the standard Foreign Function & Memory API to call math libraries, replacing complex custom code within the JVM. This makes the code easier to maintain. New Hardware Support: Operations on Float16 values (like addition and multiplication) are now automatically optimized using vector instructions on compatible x64 CPUs. The API will remain incubating until foundational features from Project Valhalla are available. Once they are, the Vector API will be adapted to use them and will then move to the preview stage. JEP 519: Compact Object Headers Compact object headers were first introduced in JDK 24 as an experimental alternative to the standard object-header layout. This cautious approach is standard for large features, allowing for thorough testing. JEP 519 makes compact object headers a first-class, production-level feature in Java 25, improving both memory use and runtime performance for Java applications, especially those with many small objects. This enhancement aligns with the goals of Project Lilliput, which aims to drastically reduce Java memory footprint. Object headers shrink from 128 bits to 64 bits on 64-bit JVMs, reducing memory and improving performance especially for deployments with many small objects. JEP 521: Generational Shenandoah Generational Shenandoah was introduced experimentally in JDK 24 (JEP 404). JDK 25 promotes it to a product-level feature, removing the need for experimental JVM flags. Shenandoah is a low-pause, concurrent garbage collector designed to minimize the traditional “stop-the-world” pauses by performing most of its work concurrently with running Java threads. It targets large heaps and latency-sensitive applications with pause times consistently under 10 ms. Generational Shenandoah extends Shenandoah by introducing generational garbage collection support, segregating heap objects by age: Young Generation: Recently created objects that tend to become unreachable quickly. Old Generation: Long-lived objects that survive multiple collections. This follows the weak generational hypothesis, which states that most objects die young. An upgraded garbage collector offering lower pause times and better memory management. Useful for: large scale, latency-sensitive applications. JEP 514: Ahead-of-Time Command-Line Ergonomics Ahead-of-Time compilation allows parts of Java bytecode to be compiled into native code prior to runtime. This reduces application startup time by avoiding some of the Just-In-Time (JIT) compilation work during application launch. Java 24 introduced the ability to record an Ahead-of-Time (AOT) cache based on an application’s workload, enabling faster startup in subsequent runs by preloading and linking classes. JEP 514 in Java 25 introduces the -XX:AOTCacheOutput command-line option, which combines both stages into a single JVM invocation that: Runs the app once to record class loading behavior internally to a temporary file. Creates the AOT cache file specified by the -XX:AOTCacheOutput option. Cleans up temporary files automatically. Example: java -XX:AOTCacheOutput=app.aot -cp app.jar MainClass Shortens a previously two-step process to just one command. JEP 515: Ahead-of-Time Method Profiling Ahead-of-Time Method Profiling is a feature introduced in Java 25 that collects method execution profiles during a training run of an application and stores this profile data in the AOT cache. This profile data includes statistics on method execution frequency, typical object types, and other runtime behavior that helps the JVM’s Just-In-Time (JIT) compiler optimize code more effectively and faster at startup. Ahead-of-Time Method Profiling lets you bootstrap the JVM at startup with pre-existing execution profiles derived from earlier training runs, speeding up the hot-spotting and JIT optimization phases. JEP 503: Remove the 32-bit x86 Port JEP 503 removes the source code and build support for the 32-bit x86 (Intel/AMD) port of the HotSpot JVM in OpenJDK. This port was deprecated in JDK 24 (JEP 501) and fully removed in JDK 25. What Was Removed? Source files specific to the x86 32-bit architecture. Build scripts and configurations to compile for 32-bit x86. Tests exclusively run for 32-bit x86. Compatibility and fallback code specific to this platform. Platforms Still Supported Other 32-bit platforms such as ARM32 remain supported. 64-bit x86 (x86-64 / AMD64) remains the primary focus on Intel/AMD hardware. Zero port (an architecture-agnostic interpreter mode) remains available but without JIT optimizations. Impact on Developers Applications running on 64-bit systems see no difference. Legacy users running 32-bit x86 JVMs should consider migrating to 64-bit JVMs. The Zero interpreter can still be used on unsupported hardware but with degraded performance. JEP 509: JFR CPU-Time Profiling Java Flight Recorder (JFR) is the JVM’s built-in profiling and diagnostics tool. Traditionally, JFR collects execution-time samples to approximate CPU usage, which samples stack traces at regular intervals of elapsed real time. JEP 509 introduces an experimental CPU-time profiling feature specifically on Linux, capturing thread stack samples based on CPU-time consumed rather than elapsed time, providing much higher accuracy for CPU usage profiling. JEP 518: JFR Cooperative Sampling Java Flight Recorder (JFR) samples Java thread stacks asynchronously by suspending threads at arbitrary points to gather stack traces. This asynchronous sampling uses heuristics to walk thread stacks when threads are not at well-defined safe states. JEP 518 redesigns the JFR sampling mechanism to improve stability and scalability by using cooperative sampling at JVM safepoints. JEP 520: JFR Method Timing & Tracing JFR (Java Flight Recorder) Method Timing & Tracing provides enhanced capabilities to precisely record execution times and traces of specific Java methods by instrumenting bytecode. These facilities improve on traditional sampling by providing exact, method-level statistics, including invocation counts and durations. JEP 520 dramatically enhances JFR’s ability to provide detailed method-level timing and trace information to developers, enabling powerful performance insights and troubleshooting capabilities while minimizing application changes and overheads. Complete Table of JEPs in Java 25 New Features With Examples JEP Feature Status Category 470 PEM Encodings of Cryptographic Objects Preview Security 502 Stable Values Preview Core Libs 503 Remove the 32-bit x86 Port Final Removal 505 Structured Concurrency Fifth Preview Concurrency 506 Scoped Values Final Concurrency 507 Primitive Types in Patterns, instanceof, and switch Third Preview Syntax 508 Vector API Incubator Performance 509 JFR CPU-Time Profiling Experimental Profiling 510 Key Derivation Function API Final Security 511 Module Import Declarations Final Syntax 512 Compact Source Files and Instance Main Methods Final Syntax 513 Flexible Constructor Bodies Final Syntax 514 Ahead-of-Time Command-Line Ergonomics Final Performance 515 Ahead-of-Time Method Profiling Final Performance 518 JFR Cooperative Sampling Final Profiling 519 Compact Object Headers Final Performance 520 JFR Method Timing & Tracing Final Profiling 521 Generational Shenandoah Final Garbage Collector Conclusion Java 25’s improvements make learning and professional development easier and faster. The new compact programs help new learners start quickly, while enhancements in concurrency, performance, and security benefit experienced developers with more robust, scalable applications. For every feature, refer to its JEP if deeper technical details are needed. Java’s evolution continues at full speed, keeping it one of the most relevant programming languages in the world today. Sources: https://docs.oracle.com/en/java/javase/25/language/java-language-changes-release.html#GUID-6459681C-6881-45D8-B0DB-395D1BD6DB9B https://www.oracle.com/java/technologies/javase/25all-relnotes.html For other version’s features, kindly go through Java Features After Java 8. Also check ‘How to add JDK 25 in Eclipse or STS?‘ to write & test Java programs using Java 25. Related