Java 25 Lts
Release Overview
Java 25 reached General Availability on September 16, 2025, as the latest LTS release. This release delivers 18 JEPs with strong focus on performance optimization, runtime improvements, and finalizing preview features.
Key Metrics:
- Release Date: September 16, 2025
- Support Duration: 8+ years (until 2033+)
- Previous LTS: Java 21 (September 2023, 2-year gap)
- Next LTS: Java 27 (expected September 2027, 2-year gap)
- JEPs Delivered: 18 enhancements (7 finalized, 11 preview/incubator)
- Major Theme: Enterprise-ready performance features
Major Language Features (Finalized)
1. Stream Gatherers (Finalized) 🚀
JEP 485: Powerful new intermediate operation for Java Streams enabling custom, stateful transformations.
Revolutionary for Streams: This is the most significant Stream API enhancement since Java 8.
Key Benefits:
- Custom intermediate operations (not just terminal)
- Built-in stateful transformations (windowing, scanning, folding)
- Short-circuiting support
- Better composability than traditional operations
Built-in Gatherers:
1. fold() - Stateful Aggregation
public record Donation(String donor, BigDecimal amount, String category) {}
public record DonationSummary(BigDecimal total, int count, BigDecimal avg) {}
public class DonationProcessor {
public DonationSummary summarize(List<Donation> donations) {
return donations.stream()
.gather(Gatherers.fold(
() -> new DonationSummary(BigDecimal.ZERO, 0, BigDecimal.ZERO),
(summary, donation) -> {
BigDecimal newTotal = summary.total().add(donation.amount());
int newCount = summary.count() + 1;
BigDecimal newAvg = newTotal.divide(
BigDecimal.valueOf(newCount), 2, RoundingMode.HALF_UP
);
return new DonationSummary(newTotal, newCount, newAvg);
}
))
.findFirst()
.orElse(new DonationSummary(BigDecimal.ZERO, 0, BigDecimal.ZERO));
}
}2. scan() - Running Totals
public record Transaction(LocalDate date, BigDecimal amount, String type) {}
public class TransactionLedger {
public List<BigDecimal> calculateRunningBalance(
List<Transaction> txs, BigDecimal initial
) {
return txs.stream()
.map(Transaction::amount)
.gather(Gatherers.scan(
() -> initial,
(balance, amount) -> balance.add(amount)
))
.toList();
}
public void printLedger(List<Transaction> txs, BigDecimal initial) {
List<BigDecimal> balances = calculateRunningBalance(txs, initial);
System.out.println("Date | Amount | Balance");
System.out.println("-----------|-----------|----------");
for (int i = 0; i < txs.size(); i++) {
Transaction tx = txs.get(i);
System.out.printf("%s | %9s | %9s%n",
tx.date(), tx.amount(), balances.get(i));
}
}
}3. windowFixed() - Fixed-Size Batching
public record Payment(String id, BigDecimal amount, String recipient) {}
public class BatchProcessor {
public void processBatched(List<Payment> payments) {
payments.stream()
.gather(Gatherers.windowFixed(10)) // Batches of 10
.forEach(batch -> {
System.out.println("Processing batch of " + batch.size());
BigDecimal total = batch.stream()
.map(Payment::amount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Batch total: $" + total);
submitBatch(batch);
});
}
}4. windowSliding() - Moving Window Analysis
public record PricePoint(LocalDateTime timestamp, BigDecimal price) {}
public class MovingAverageCalculator {
public List<BigDecimal> calculateMovingAverage(
List<PricePoint> prices, int window
) {
return prices.stream()
.gather(Gatherers.windowSliding(window))
.map(w -> {
BigDecimal sum = w.stream()
.map(PricePoint::price)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(
BigDecimal.valueOf(w.size()), 2, RoundingMode.HALF_UP
);
})
.toList();
}
}5. mapConcurrent() - Concurrent Transformation
public record Customer(String id, String name) {}
public record EnrichedCustomer(String id, String name,
BigDecimal balance, List<String> txs) {}
public class ConcurrentDataEnricher {
public List<EnrichedCustomer> enrich(List<Customer> customers) {
return customers.stream()
.gather(Gatherers.mapConcurrent(
10, // Max concurrency
customer -> {
// Expensive I/O operations
BigDecimal balance = fetchBalance(customer.id());
List<String> txs = fetchTransactions(customer.id());
return new EnrichedCustomer(
customer.id(), customer.name(), balance, txs
);
}
))
.toList();
}
}Custom Gatherer Example - Zakat Calculation:
public class ZakatGatherer {
public record Asset(String type, BigDecimal value) {}
public record ZakatResult(BigDecimal totalAssets, BigDecimal zakatDue) {}
public static Gatherer<Asset, ?, ZakatResult> calculateZakat(
BigDecimal nisabThreshold
) {
return Gatherer.of(
() -> new ZakatState(nisabThreshold),
(state, asset, downstream) -> {
state.addAsset(asset);
if (!state.meetsNisab()) {
return false; // Short-circuit
}
return true;
},
(state, downstream) -> {
if (state.meetsNisab()) {
downstream.push(state.calculateZakat());
}
}
);
}
private static class ZakatState {
private final BigDecimal nisabThreshold;
private BigDecimal totalAssets = BigDecimal.ZERO;
private static final BigDecimal RATE = new BigDecimal("0.025");
ZakatState(BigDecimal threshold) {
this.nisabThreshold = threshold;
}
void addAsset(Asset asset) {
totalAssets = totalAssets.add(asset.value());
}
boolean meetsNisab() {
return totalAssets.compareTo(nisabThreshold) >= 0;
}
ZakatResult calculateZakat() {
BigDecimal due = totalAssets.multiply(RATE);
return new ZakatResult(totalAssets, due);
}
}
}When to Use Stream Gatherers:
- ✅ Stateful intermediate operations
- ✅ Custom transformations beyond map/filter/reduce
- ✅ Performance-critical pipelines
- ✅ Complex aggregations with state
- ✅ Batch processing with windows
2. Compact Source Files and Instance Main Methods
JEP 512: Simplified program structure for beginners and scripts.
Traditional Hello World:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}Java 25 Compact Source:
void main() {
println("Hello, World!");
}Real-World Example - Zakat Calculator:
void main() {
println("=== Zakat Calculator ===");
var wealth = new BigDecimal("100000");
var nisab = new BigDecimal("85"); // grams of gold
var goldPrice = new BigDecimal("65"); // per gram
var threshold = nisab.multiply(goldPrice);
if (wealth.compareTo(threshold) >= 0) {
var zakat = wealth.multiply(new BigDecimal("0.025"));
println("Zakat due: $" + zakat);
} else {
println("Below nisab");
}
}Instance State in Compact Files:
private BigDecimal balance = BigDecimal.ZERO;
void main() {
deposit(new BigDecimal("1000"));
withdraw(new BigDecimal("250"));
println("Final balance: $" + balance);
}
void deposit(BigDecimal amount) {
balance = balance.add(amount);
println("Deposited: $" + amount);
}
void withdraw(BigDecimal amount) {
balance = balance.subtract(amount);
println("Withdrew: $" + amount);
}3. Flexible Constructor Bodies
JEP 513: Place statements before super() or this() calls for validation.
Before Java 25:
public class ZakatAccount {
private final String accountNumber;
private final BigDecimal initialBalance;
public ZakatAccount(String accountNumber, BigDecimal initialBalance) {
super(); // Must be first
// Validation AFTER super() - too late!
if (accountNumber == null) {
throw new IllegalArgumentException("Account required");
}
this.accountNumber = accountNumber;
this.initialBalance = initialBalance;
}
}Java 25 - Flexible Constructor Bodies:
public class ZakatAccount {
private final String accountNumber;
private final BigDecimal initialBalance;
public ZakatAccount(String accountNumber, BigDecimal initialBalance) {
// Validation BEFORE super()
if (accountNumber == null || accountNumber.isEmpty()) {
throw new IllegalArgumentException("Account required");
}
if (initialBalance.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Negative balance");
}
super(); // Call after validation
this.accountNumber = accountNumber;
this.initialBalance = initialBalance;
}
}Complex Example:
public class ZakatAccount extends Account {
private final BigDecimal nisabThreshold;
private final ZakatCalculator calculator;
public ZakatAccount(String id, BigDecimal goldPrice, int year) {
// Calculate before super()
BigDecimal nisabInGold = new BigDecimal("85");
BigDecimal calculatedNisab = nisabInGold.multiply(goldPrice);
// Validate
if (goldPrice.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Invalid gold price");
}
if (year < 2000 || year > 2100) {
throw new IllegalArgumentException("Invalid year");
}
// Create calculator
ZakatCalculator tempCalc = new ZakatCalculator(year);
super(id, "ZAKAT"); // Delegate to parent
// Initialize fields
this.nisabThreshold = calculatedNisab;
this.calculator = tempCalc;
}
}4. Scoped Values (Finalized)
JEP 506: Safer, more efficient alternative to ThreadLocal variables.
Benefits:
- Immutable by default
- Better performance with virtual threads
- Clearer scope lifecycle
- No memory leaks
Example:
public class RequestContext {
public static final ScopedValue<User> CURRENT_USER =
ScopedValue.newInstance();
public static final ScopedValue<String> REQUEST_ID =
ScopedValue.newInstance();
public void handleRequest(User user, String requestId, Runnable handler) {
ScopedValue.where(CURRENT_USER, user)
.where(REQUEST_ID, requestId)
.run(handler);
}
public void processTransaction() {
User user = CURRENT_USER.get();
String requestId = REQUEST_ID.get();
System.out.println("Processing for " + user.name() +
" (Request: " + requestId + ")");
}
}5. Module Import Declarations
JEP 511: Import all packages from module with single declaration.
Example:
// Before - import each package
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.stream.Collectors;
// Java 25 - import entire module
import module java.base;
public class DataProcessor {
public void process() {
List<String> data = new ArrayList<>();
Map<String, Integer> counts = new HashMap<>();
// All java.base classes available
}
}Performance and Runtime Improvements (Finalized)
6. Compact Object Headers 🎯
JEP 519: Reduces object header size from 96 bits to 64 bits, reducing memory by 10-20%.
Automatic Benefit - No Code Changes!
Impact:
// Before Java 25: header = 96 bits (12 bytes)
// Java 25: header = 64 bits (8 bytes)
public class DonationRecord {
private String id; // 8 bytes reference
private BigDecimal amount; // 8 bytes reference
private LocalDate date; // 8 bytes reference
// Header: 8 bytes (was 12 bytes)
// Total: 32 bytes (was 36 bytes) - 11% reduction!
}
// Application with 1 million DonationRecord instances:
// Java 17/21: ~180 MB heap
// Java 25: ~144 MB heap (20% savings!)Benefits:
- 10-20% memory reduction
- Better cache utilization
- Improved GC performance
- More objects fit in memory
7. Ahead-of-Time Method Profiling
JEP 515: Records method behavior in advance, reducing warm-up time by 30-50%.
Benefits:
- Faster startup
- Reduced warm-up time
- Better peak performance
- Lower CPU during startup
No code changes - JVM automatically uses pre-recorded profiling data.
8. Generational Shenandoah
JEP 521: Shenandoah GC with generational mode for better throughput and lower pauses.
Benefits:
- 20-30% better throughput
- Lower pause times
- Reduced memory overhead
- Improved for most workloads
Enable:
java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational YourApp9. Key Derivation Function API
JEP 510: Standardized API for key derivation functions.
Example:
import javax.crypto.KDF;
import javax.crypto.SecretKey;
import javax.crypto.spec.KDFParametersSpec;
public class SecurePasswordManager {
public SecretKey deriveKey(char[] password, byte[] salt)
throws Exception {
KDF kdf = KDF.getInstance("PBKDF2WithHmacSHA256");
KDFParametersSpec params = KDFParametersSpec.ofPBKDF2(
password, salt, 100000, 256
);
return kdf.deriveKey("AES", params);
}
}Preview Features
10. Primitive Types in Patterns (Third Preview)
JEP 507: Pattern matching with primitive types.
Example:
public class PaymentValidator {
public String validate(Object amount) {
return switch (amount) {
case int i when i > 0 ->
"Valid integer: " + i;
case long l when l > 0 ->
"Valid long: " + l;
case double d when d > 0.0 ->
"Valid double: " + d;
case int i -> "Invalid integer: " + i;
case long l -> "Invalid long: " + l;
case double d -> "Invalid double: " + d;
default -> "Unknown type";
};
}
// instanceof with primitives
public boolean isPositive(Object obj) {
if (obj instanceof int i) {
return i > 0;
} else if (obj instanceof double d) {
return d > 0.0;
}
return false;
}
}11. Structured Concurrency (Fifth Preview)
JEP 505: Continues refinement of structured concurrency API.
12. Stable Values (Preview)
JEP 502: For frequently-accessed, rarely-changed data.
Performance Improvements Summary
Java 25 delivers substantial performance gains:
| Feature | Improvement |
|---|---|
| AOT Method Profiling | 30-50% faster warm-up |
| Compact Object Headers | 10-20% memory reduction |
| Generational Shenandoah | 20-30% better throughput |
| Scoped Values | 2-3x faster than ThreadLocal |
| Overall Startup | 20-40% faster |
Benchmark - Finance Application:
| Metric | Java 17 | Java 21 | Java 25 | Improvement |
|---|---|---|---|---|
| Startup Time | 2.3s | 2.1s | 1.7s | -26% |
| Heap (1M objects) | 180 MB | 180 MB | 144 MB | -20% |
| GC Pause (p99) | 45 ms | 38 ms | 28 ms | -38% |
| Throughput | 12.5K req/s | 14K req/s | 16.2K req/s | +30% |
| First Request | 3.8s | 3.2s | 2.1s | -45% |
Migration from Java 21
New Capabilities:
- Stream Gatherers for complex transformations
- Compact headers (automatic 10-20% memory savings)
- AOT profiling (faster startup)
- Flexible constructor bodies
- Scoped Values finalized
Migration Steps:
- Test performance with AOT profiling
- Adopt flexible constructors for validation
- Consider module imports
- Migrate ThreadLocal to ScopedValue
- Test Generational Shenandoah
Why Upgrade to Java 25?
For Java 21 Applications:
- Performance: 20-40% better in many workloads
- Memory: 10-20% footprint reduction
- Startup: Significantly faster with AOT
- Stream Gatherers: Powerful new streaming
- Long-term Support: 8+ years
For New Projects:
- Latest LTS: Most modern version
- Performance-First: Best runtime performance
- Modern Language: All finalized features
- Framework Support: Spring Boot 3.x+
- Cloud-Native: Optimized for containers
For Java 17 Applications:
- Two-phase recommended (17 → 21 → 25)
- Direct migration possible
- Gain all concurrency and performance improvements
Feature Evolution
| Feature | Java 17 | Java 21 | Java 25 |
|---|---|---|---|
| Virtual Threads | ❌ None | ✅ Finalized | ✅ Optimized |
| Pattern Matching | 🔬 Preview | ✅ Finalized | ✅ Enhanced |
| Sequenced Collections | ❌ None | ✅ Finalized | ✅ Available |
| Stream Gatherers | ❌ None | ❌ None | ✅ Finalized |
| Compact Headers | ❌ None | ❌ None | ✅ Finalized |
| Scoped Values | ❌ None | 🔬 Preview | ✅ Finalized |
| Flexible Constructors | ❌ None | ❌ None | ✅ Finalized |
Enterprise Readiness
Java 25 is production-ready, enterprise-focused:
- 7 features graduated from preview/incubator
- 9 features focused on performance
- Validated performance improvements
- Strong ecosystem support
- Long-term support commitment
Summary
Java 25 LTS (2025) optimizes Java for enterprise with:
- Stream Gatherers for powerful data processing
- Compact headers for automatic memory savings
- AOT profiling for faster startup
- Flexible constructors for better validation
- Scoped Values finalized for efficient context
- Performance gains of 20-40% across metrics
Next Steps:
- Review Java 17 Highlights for foundation
- Explore Java 21 Highlights for virtual threads