Released on September 16, 2025.
- Java 25 is a Long-Term Support (LTS) release, continuing Java's evolution with a focus on simplicity and performance.
- It brings significant improvements in language expressiveness, performance, observability, and security.
- This version introduces features like Primitive Types in Patterns, Flexible Constructor Bodies, and Generational Shenandoah GC.
1. Primitive Types in Patterns JEP 507 (Preview)
- Primitive Types in Patterns allow pattern matching with primitive types (
int,double,long, etc.) ininstanceofandswitch. - This feature removes previous restrictions and makes code cleaner when mixing primitives and reference types.
- Note: This is a preview feature in Java 25, use
--enable-previewflag.
public void processValue(Object obj) {
if (obj instanceof Integer) {
Integer boxed = (Integer) obj;
int i = boxed; // Unboxing needed
System.out.println("Integer value: " + i);
} else if (obj instanceof Double) {
Double boxed = (Double) obj;
double d = boxed; // Unboxing needed
System.out.println("Double value: " + d);
}
}public void processValue(Object obj) {
if (obj instanceof int i) { // Direct primitive pattern
System.out.println("Integer value: " + i);
} else if (obj instanceof double d) { // Direct primitive pattern
System.out.println("Double value: " + d);
} else if (obj instanceof long l) {
System.out.println("Long value: " + l);
}
}public String describeNumber(Object obj) {
return switch (obj) {
case int i when i > 0 -> "Positive integer: " + i;
case int i when i < 0 -> "Negative integer: " + i;
case int i -> "Zero";
case double d when d > 0.0 -> "Positive double: " + d;
case double d -> "Non-positive double: " + d;
case long l -> "Long value: " + l;
default -> "Not a number type";
};
}
// Usage
System.out.println(describeNumber(42)); // Positive integer: 42
System.out.println(describeNumber(-5)); // Negative integer: -5
System.out.println(describeNumber(3.14)); // Positive double: 3.14
System.out.println(describeNumber(100L)); // Long value: 100public class DataProcessor {
public void processData(List<Object> data) {
for (Object item : data) {
switch (item) {
case int i when i > 1000 ->
System.out.println("Large number: " + i);
case int i ->
System.out.println("Small number: " + i);
case double d ->
System.out.printf("Decimal: %.2f%n", d);
case String s ->
System.out.println("Text: " + s);
case null ->
System.out.println("Null value");
default ->
System.out.println("Unknown type");
}
}
}
}
// Usage
List<Object> data = List.of(42, 3.14, "Hello", 2000, null, 999);
new DataProcessor().processData(data);2. Module Import Declarations JEP 511
- Module Import Declarations introduce
import module <module-name>syntax for cleaner module usage. - Simplifies code that uses Java Platform Module System (JPMS).
- Makes it easier to work with modular applications.
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// ... many more imports
public class MyApp {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
// ...
}
}import module java.base; // Import entire module
public class MyApp {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Map<String, Integer> scores = new HashMap<>();
// All java.base classes are available
}
}// In your module-info.java
module com.mycompany.app {
requires com.mycompany.utils;
requires java.sql;
}
// In your source file
import module com.mycompany.utils; // Import all exported packages
public class BusinessLogic {
public void process() {
// Use utility classes without individual imports
StringUtils.capitalize("hello");
DateUtils.formatNow();
}
}3. Flexible Constructor Bodies JEP 513
- Flexible Constructor Bodies allow statements before
super()orthis()calls in constructors. - Enables validation and computation before delegating to another constructor.
- Reduces boilerplate and makes validation more natural.
public class User {
private final String name;
private final int age;
public User(String name, int age) {
// Can't validate before super()
super();
// Validation after super()
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age");
}
this.name = name;
this.age = age;
}
}public class User {
private final String name;
private final int age;
public User(String name, int age) {
// Validation BEFORE super()!
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age");
}
super(); // Now we can call super after validation
this.name = name;
this.age = age;
}
}public class Rectangle {
private final double width;
private final double height;
// Main constructor
public Rectangle(double width, double height) {
// Validate before super()
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Dimensions must be positive");
}
super();
this.width = width;
this.height = height;
}
// Delegating constructor with computation before this()
public Rectangle(double side) {
// Compute and validate before delegating
double normalizedSide = Math.abs(side);
if (normalizedSide == 0) {
normalizedSide = 1.0; // Default value
}
this(normalizedSide, normalizedSide); // Delegate after computation
}
}
// Usage
Rectangle rect1 = new Rectangle(10); // Creates 10x10 square
Rectangle rect2 = new Rectangle(-5); // Creates 5x5 square (normalized)public class BankAccount {
private final String accountNumber;
private final String ownerName;
private final double initialBalance;
public BankAccount(String accountNumber, String ownerName, double initialBalance) {
// Complex validation before super()
if (accountNumber == null || !accountNumber.matches("[0-9]{10}")) {
throw new IllegalArgumentException("Invalid account number format");
}
String trimmedName = ownerName != null ? ownerName.trim() : "";
if (trimmedName.length() < 2) {
throw new IllegalArgumentException("Owner name too short");
}
if (initialBalance < 0) {
throw new IllegalArgumentException("Initial balance cannot be negative");
}
super();
this.accountNumber = accountNumber;
this.ownerName = trimmedName;
this.initialBalance = initialBalance;
}
}4. Instance Main Methods & Compact Source Files JEP 512
- Instance Main Methods allow
main()to be an instance method instead of static. - Compact Source Files reduce ceremony for simple programs - great for learning and scripting.
- Makes Java more beginner-friendly and reduces boilerplate.
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}// No 'public', no 'static', no 'String[] args' required!
class Hello {
void main() {
System.out.println("Hello, Java 25!");
}
}class Calculator {
private int result = 0;
void main() {
add(10);
multiply(5);
subtract(15);
System.out.println("Final result: " + result);
}
void add(int value) {
result += value;
System.out.println("After add: " + result);
}
void multiply(int value) {
result *= value;
System.out.println("After multiply: " + result);
}
void subtract(int value) {
result -= value;
System.out.println("After subtract: " + result);
}
}// Many common packages are imported by default
class DataProcessor {
void main() {
// List, Map, etc. are automatically available
List<String> names = List.of("Alice", "Bob", "Charlie");
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);
// Stream operations work too
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
}
}5. Scoped Values (Final) JEP 506
- Scoped Values provide immutable, shareable context across threads - finalized in Java 25.
- Better alternative to
ThreadLocalfor virtual threads and concurrent programming. - Automatic cleanup and inheritance across thread boundaries.
// Traditional ThreadLocal - manual cleanup needed
public class UserContext {
private static final ThreadLocal<String> CURRENT_USER = new ThreadLocal<>();
public static void setUser(String user) {
CURRENT_USER.set(user);
}
public static String getUser() {
return CURRENT_USER.get();
}
public static void clear() {
CURRENT_USER.remove(); // Easy to forget!
}
}public class UserContext {
public static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
public static void processRequest(String userId) {
ScopedValue.where(CURRENT_USER, userId).run(() -> {
// User ID is available in this scope
handleRequest();
// Automatically inherited by virtual threads!
Thread.startVirtualThread(() -> {
String user = CURRENT_USER.get();
System.out.println("Processing for user: " + user);
});
});
// Automatically cleaned up after scope
}
private static void handleRequest() {
String user = CURRENT_USER.get();
System.out.println("Handling request for: " + user);
}
}public class WebRequestHandler {
private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
private static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();
public void handleRequest(String requestId, String userId, String traceId) {
ScopedValue.where(REQUEST_ID, requestId)
.where(USER_ID, userId)
.where(TRACE_ID, traceId)
.run(() -> {
processRequest();
});
}
private void processRequest() {
log("Starting request processing");
authenticateUser();
fetchData();
log("Request processing complete");
}
private void authenticateUser() {
log("Authenticating user");
// Authentication logic
}
private void fetchData() {
log("Fetching data from database");
// Database operations
}
private void log(String message) {
String requestId = REQUEST_ID.get();
String userId = USER_ID.get();
String traceId = TRACE_ID.get();
System.out.printf("[%s][%s][%s] %s%n", traceId, requestId, userId, message);
}
}
// Usage
WebRequestHandler handler = new WebRequestHandler();
handler.handleRequest("REQ-001", "user123", "TRACE-XYZ");6. Structured Concurrency (5th Preview) JEP 505
- Structured Concurrency treats concurrent tasks as a single unit of work.
- Simplifies error handling and cancellation in multi-threaded code.
- Works seamlessly with virtual threads.
public UserData fetchUserData(String userId) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
try {
Future<Profile> profileFuture = executor.submit(() -> fetchProfile(userId));
Future<Orders> ordersFuture = executor.submit(() -> fetchOrders(userId));
Future<Preferences> prefsFuture = executor.submit(() -> fetchPreferences(userId));
Profile profile = profileFuture.get();
Orders orders = ordersFuture.get();
Preferences prefs = prefsFuture.get();
return new UserData(profile, orders, prefs);
} finally {
executor.shutdown();
}
}public UserData fetchUserData(String userId) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// Fork all tasks
Supplier<Profile> profile = scope.fork(() -> fetchProfile(userId));
Supplier<Orders> orders = scope.fork(() -> fetchOrders(userId));
Supplier<Preferences> prefs = scope.fork(() -> fetchPreferences(userId));
// Wait for all to complete
scope.join().throwIfFailed();
// All tasks succeeded - collect results
return new UserData(profile.get(), orders.get(), prefs.get());
} // Automatic cleanup
}public class MultiServerQuery {
public String queryFastestServer(List<String> serverUrls) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
// Query all servers in parallel
for (String url : serverUrls) {
scope.fork(() -> queryServer(url));
}
// Wait for first success, cancel others
scope.join();
return scope.result();
}
}
private String queryServer(String url) {
System.out.println("Querying: " + url);
// Simulate API call
return "Response from " + url;
}
}7. Generational Shenandoah GC JEP 521
- Generational Shenandoah is a low-pause garbage collector optimized for large heaps.
- Separates young and old generations for better performance.
- Excellent for latency-sensitive applications.
# Run Java with Generational Shenandoah GC
java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational MyApppublic class HighThroughputApp {
public static void main(String[] args) {
// Benefits of Generational Shenandoah:
// 1. Lower pause times (typically < 10ms)
// 2. Better throughput for apps with many short-lived objects
// 3. More predictable performance
// This app creates many temporary objects
for (int i = 0; i < 1_000_000; i++) {
String temp = "Processing item " + i;
processBatch(generateData(1000));
}
}
static List<String> generateData(int size) {
List<String> data = new ArrayList<>();
for (int i = 0; i < size; i++) {
data.add("Item " + i); // Short-lived objects
}
return data;
}
static void processBatch(List<String> batch) {
// Process data
}
}# Basic configuration
java -XX:+UseShenandoahGC \
-XX:ShenandoahGCMode=generational \
-Xms4g -Xmx4g \
MyApp
# With additional tuning
java -XX:+UseShenandoahGC \
-XX:ShenandoahGCMode=generational \
-XX:+AlwaysPreTouch \
-XX:+UseNUMA \
-Xlog:gc*:file=gc.log \
MyApp8. Compact Object Headers JEP 519
- Compact Object Headers reduce object header size from 96-128 bits to 64 bits.
- Reduces memory footprint and improves cache locality.
- Particularly beneficial for applications with many small objects.
public class MemoryComparison {
// Each object typically has a 12-16 byte header
static class SmallObject {
int value; // 4 bytes
// Before: ~12-16 byte header + 4 bytes data = 16-20 bytes per object
// After: ~8 byte header + 4 bytes data = 12 bytes per object
}
public static void main(String[] args) {
// Creating millions of small objects
List<SmallObject> objects = new ArrayList<>();
for (int i = 0; i < 10_000_000; i++) {
objects.add(new SmallObject());
}
// With compact headers:
// Saves ~4-8 bytes per object = 40-80 MB saved for 10M objects
System.out.println("Objects created: " + objects.size());
}
}public class LargeDataset {
record Person(String name, int age) {}
public static void main(String[] args) {
// Applications with large collections benefit the most
List<Person> people = new ArrayList<>();
for (int i = 0; i < 5_000_000; i++) {
people.add(new Person("Person" + i, 25 + (i % 50)));
}
// With compact headers:
// - Reduced memory usage
// - Better CPU cache utilization
// - Improved performance for iteration and access
long count = people.stream()
.filter(p -> p.age() > 40)
.count();
System.out.println("People over 40: " + count);
}
}9. Key Derivation Function API JEP 510
- Key Derivation Function (KDF) API provides standard KDF operations in Java.
- Includes PBKDF2, HKDF, and other algorithms.
- Improves security for password hashing and key generation.
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
public class PasswordHasher {
private static final int ITERATIONS = 65536;
private static final int KEY_LENGTH = 256;
public static String hashPassword(String password, byte[] salt) throws Exception {
KeySpec spec = new PBEKeySpec(
password.toCharArray(),
salt,
ITERATIONS,
KEY_LENGTH
);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(hash);
}
public static byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return salt;
}
public static void main(String[] args) throws Exception {
String password = "MySecurePassword123!";
byte[] salt = generateSalt();
String hashedPassword = hashPassword(password, salt);
System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt));
System.out.println("Hashed Password: " + hashedPassword);
}
}import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class KeyDerivation {
public static SecretKey deriveKey(byte[] inputKey, byte[] salt, byte[] info, int length)
throws NoSuchAlgorithmException, InvalidKeyException {
// HKDF-based key derivation
// Extract phase
SecretKey prk = extract(inputKey, salt);
// Expand phase
return expand(prk, info, length);
}
private static SecretKey extract(byte[] inputKey, byte[] salt) {
// HKDF-Extract implementation
// Returns pseudo-random key
return new SecretKeySpec(inputKey, "HmacSHA256");
}
private static SecretKey expand(SecretKey prk, byte[] info, int length) {
// HKDF-Expand implementation
// Expands PRK to desired length
return prk;
}
}10. Vector API (10th Incubator) JEP 508
- Vector API enables SIMD (Single Instruction, Multiple Data) operations.
- Dramatically improves performance for numerical computations.
- Excellent for AI, machine learning, and data processing workloads.
import jdk.incubator.vector.*;
public class VectorExample {
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
// Traditional scalar approach
public static void scalarMultiply(float[] a, float[] b, float[] result) {
for (int i = 0; i < a.length; i++) {
result[i] = a[i] * b[i];
}
}
// Vector API approach - MUCH FASTER
public static void vectorMultiply(float[] a, float[] b, float[] result) {
int i = 0;
int upperBound = SPECIES.loopBound(a.length);
// Process vectors
for (; i < upperBound; i += SPECIES.length()) {
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(vb);
vc.intoArray(result, i);
}
// Process remaining elements
for (; i < a.length; i++) {
result[i] = a[i] * b[i];
}
}
}public class MatrixOperations {
static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED;
public static double[] matrixVectorMultiply(double[][] matrix, double[] vector) {
int rows = matrix.length;
int cols = matrix[0].length;
double[] result = new double[rows];
for (int i = 0; i < rows; i++) {
double sum = 0.0;
int j = 0;
int upperBound = SPECIES.loopBound(cols);
// Vector processing
var vsum = DoubleVector.zero(SPECIES);
for (; j < upperBound; j += SPECIES.length()) {
var vrow = DoubleVector.fromArray(SPECIES, matrix[i], j);
var vvec = DoubleVector.fromArray(SPECIES, vector, j);
vsum = vrow.mul(vvec).add(vsum);
}
sum = vsum.reduceLanes(VectorOperators.ADD);
// Process remaining elements
for (; j < cols; j++) {
sum += matrix[i][j] * vector[j];
}
result[i] = sum;
}
return result;
}
}public class VectorPerformanceBenchmark {
public static void main(String[] args) {
int size = 10_000_000;
float[] a = new float[size];
float[] b = new float[size];
float[] result = new float[size];
// Initialize arrays
for (int i = 0; i < size; i++) {
a[i] = i * 1.5f;
b[i] = i * 2.5f;
}
// Scalar approach
long start = System.nanoTime();
VectorExample.scalarMultiply(a, b, result);
long scalarTime = System.nanoTime() - start;
// Vector approach
start = System.nanoTime();
VectorExample.vectorMultiply(a, b, result);
long vectorTime = System.nanoTime() - start;
System.out.printf("Scalar time: %.2f ms%n", scalarTime / 1_000_000.0);
System.out.printf("Vector time: %.2f ms%n", vectorTime / 1_000_000.0);
System.out.printf("Speedup: %.2fx%n", (double) scalarTime / vectorTime);
// Typical speedup: 2-8x depending on CPU architecture
}
}Java 25 includes several Java Flight Recorder (JFR) improvements for better observability.
JFR CPU-Time Profiling JEP 509
- More accurate CPU time measurement
- Better profiling for multithreaded applications
- Reduced overhead during profiling
public class ProfilingExample {
public static void main(String[] args) {
// Enable JFR programmatically
// Run with: java -XX:StartFlightRecording=filename=recording.jfr
// CPU-intensive work
for (int i = 0; i < 1_000_000; i++) {
heavyComputation(i);
}
}
static double heavyComputation(int input) {
double result = 0;
for (int i = 0; i < 1000; i++) {
result += Math.sqrt(input + i) * Math.log(input + i + 1);
}
return result;
}
}JFR Method Timing & Tracing JEP 520
# Enable method timing for specific packages
java -XX:StartFlightRecording=filename=trace.jfr \
-XX:FlightRecorderOptions=stackdepth=256 \
-XX:+FlightRecorder \
MyApplication
# Analyze with jfr tool
jfr print --events jdk.ExecutionSample trace.jfr12. Stable Values (Preview) JEP 502
- Stable Values are immutable values treated as constants by the JVM.
- Enables aggressive optimizations while maintaining flexibility.
- Better than final fields for lazy initialization patterns.
public class Configuration {
// Must be initialized in constructor
private final String apiKey;
private final int maxConnections;
public Configuration() {
this.apiKey = loadApiKey(); // Computed during construction
this.maxConnections = 100;
}
private String loadApiKey() {
// Complex initialization
return System.getenv("API_KEY");
}
}import jdk.internal.vm.annotation.Stable;
public class Configuration {
// Can be initialized after construction
@Stable private String apiKey;
@Stable private int maxConnections;
public void initialize() {
// Lazy initialization
if (this.apiKey == null) {
this.apiKey = loadApiKey();
this.maxConnections = 100;
}
}
private String loadApiKey() {
return System.getenv("API_KEY");
}
}-
Primitive Types in Patterns ⭐⭐⭐
- Direct pattern matching with primitives
- Cleaner switch expressions
- Reduces boilerplate code
-
Flexible Constructor Bodies ⭐⭐⭐
- Pre-super validation
- More natural constructor flow
- Better error handling
-
Generational Shenandoah GC ⭐⭐
- Low-latency garbage collection
- Better for large heaps
- Predictable performance
-
Scoped Values ⭐⭐
- Thread-safe context sharing
- Better than ThreadLocal
- Perfect for virtual threads
-
Vector API ⭐
- SIMD operations in Java
- Massive performance gains
- Important for AI/ML workloads
- LTS Release: Java 25 continues the LTS cadence with enterprise-ready features
- Performance: Compact headers and Vector API bring significant speed improvements
- Developer Experience: Simpler syntax with flexible constructors and instance main
- Observability: Enhanced JFR capabilities for better production monitoring
- Security: Standardized KDF API for modern cryptography
-
"What's new in Java 25?"
- Lead with Primitive Types in Patterns and Flexible Constructors
- Mention Generational Shenandoah GC
- Discuss Scoped Values becoming final
- Emphasize it's an LTS release
-
"How do Primitive Patterns improve code?"
- Eliminates boxing/unboxing
- Cleaner switch expressions
- More type-safe
- Better performance
-
"What are Scoped Values and why use them?"
- Immutable thread-safe context
- Automatic cleanup
- Better than ThreadLocal for virtual threads
- No memory leaks
-
"Explain Flexible Constructor Bodies"
- Validation before super()
- More natural constructor flow
- Reduces boilerplate
- Better encapsulation
-
"When would you use Vector API?"
- Numerical computations
- AI/ML workloads
- Image/audio processing
- Any SIMD-friendly operations
Key Changes:
- Primitive patterns in switch (preview)
- Flexible constructor bodies
- Scoped Values now final
- Generational Shenandoah GC available
- Compact object headers reduce memory
Benefits:
- Cleaner, more expressive code
- Better performance (GC, memory, compute)
- Enhanced security (KDF API)
- Improved observability (JFR enhancements)
- LTS support until 2033+
// JAVA 21 Style
public void processValue(Object obj) {
if (obj instanceof Integer boxed) {
int i = boxed; // Unboxing needed
System.out.println("Value: " + i);
}
}
public class User {
private final String name;
public User(String name) {
super();
if (name == null) {
throw new IllegalArgumentException();
}
this.name = name;
}
}
// JAVA 25 Style - much cleaner!
public void processValue(Object obj) {
if (obj instanceof int i) { // Direct primitive pattern
System.out.println("Value: " + i);
}
}
public class User {
private final String name;
public User(String name) {
if (name == null) { // Validation before super()
throw new IllegalArgumentException();
}
super();
this.name = name;
}
}Java 25 is a major LTS release that brings together modern language features and performance improvements:
✅ Primitive Types in Patterns - Cleaner pattern matching
✅ Flexible Constructor Bodies - Natural validation flow
✅ Scoped Values - Thread-safe context (final)
✅ Generational Shenandoah - Low-latency GC
✅ Compact Object Headers - Reduced memory footprint
✅ Vector API - SIMD performance gains
✅ Enhanced JFR - Better observability
✅ KDF API - Modern cryptography support
✅ LTS Support - Production-ready until 2033+
Perfect for interviews because:
- Latest LTS release with modern features
- Combines language improvements and performance gains
- Shows Java's evolution toward simplicity
- Demonstrates commitment to performance and security
- Important for production applications
Resources: