Torna al blog

Java 25: Novità e Miglioramenti - La Release LTS del 2025

Scopri Java 25: Flexible Constructor Bodies, Compact Source Files, Scoped Values, Stream Gatherers, Module Imports e tutte le novità della release LTS di settembre 2025.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

13 min di lettura
Java 25: Novità e Miglioramenti - La Release LTS del 2025

Java 25 (JDK 25) è stato rilasciato il 16 settembre 2025 ed è una release Long-Term Support (LTS) con almeno 8 anni di supporto da Oracle. Questa versione introduce 18 JEP (JDK Enhancement Proposals) con focus su produttività, performance e supporto AI.

🎯 Novità Principali

Flexible Constructor Bodies (JEP 513) ✅ Finalized

Finalmente puoi eseguire codice prima di super() o this() nei costruttori!

❌ Prima di Java 25 - super() deve essere PRIMO:

public class Employee extends Person {
    private final String employeeId;

    public Employee(String name, int age, String employeeId) {
        // ❌ Impossibile validare prima di super()
        super(name, age);

        // Validazione DOPO super() - sprecata costruzione se invalido
        if (employeeId == null || employeeId.isEmpty()) {
            throw new IllegalArgumentException("Invalid ID");
        }
        this.employeeId = employeeId;
    }
}

✅ Java 25 - Prologue prima di super():

public class Employee extends Person {
    private final String employeeId;

    public Employee(String name, int age, String employeeId) {
        // ✅ PROLOGUE - Validazione PRIMA di super()
        if (age < 18 || age > 67) {
            throw new IllegalArgumentException("Age must be 18-67");
        }
        if (employeeId == null || employeeId.isEmpty()) {
            throw new IllegalArgumentException("Invalid ID");
        }

        // Ora costruisci superclass solo se valido
        super(name, age);

        // EPILOGUE - Dopo super()
        this.employeeId = employeeId;
    }
}

Vantaggi:

  • Fail-fast: Evita costruzione superclass se argomenti invalidi
  • Performance: Non spreca risorse su oggetti che saranno scartati
  • Codice pulito: Niente più helper methods statici per validation

Inizializza field prima di super():

public class SmallCoffee extends Coffee {
    private final String topping;

    public SmallCoffee(int water, int milk, String topping) {
        // ✅ Inizializza field PRIMA di super()
        this.topping = topping;

        // Validazione
        if (water + milk > 100) {
            throw new IllegalArgumentException("Volume too large");
        }

        super(water, milk);
    }
}

Limitazioni Prologue:

// ❌ NON puoi nel prologue:
// - Leggere field (solo scrivere)
// - Chiamare metodi instance
// - Usare 'this' (tranne per assegnare field)

public class Example extends Base {
    private int value = 10;

    public Example(int x) {
        // ❌ ERRORE: non puoi leggere field
        if (this.value > x) { ... }

        // ❌ ERRORE: non puoi chiamare metodi instance
        this.validate(x);

        // ✅ OK: puoi scrivere field
        this.value = x;

        super();
    }
}

Compact Source Files & Instance Main (JEP 512) ✅ Finalized

Java diventa linguaggio di scripting per principianti e utility!

❌ Prima - Boilerplate pesante:

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

✅ Java 25 - Zero boilerplate:

// hello.java - BASTA QUESTO!
void main() {
    println("Hello, World!");
}

Esegui direttamente:

java hello.java
# Output: Hello, World!

Esempio: Script calculator

// calc.java
void main(String[] args) {
    if (args.length < 3) {
        println("Usage: java calc.java <num1> <op> <num2>");
        return;
    }

    double a = Double.parseDouble(args[0]);
    String op = args[1];
    double b = Double.parseDouble(args[2]);

    double result = switch (op) {
        case "+" -> a + b;
        case "-" -> a - b;
        case "*" -> a * b;
        case "/" -> a / b;
        default -> throw new IllegalArgumentException("Invalid op");
    };

    println(STR."\{a} \{op} \{b} = \{result}");
}
java calc.java 10 + 5
# Output: 10.0 + 5.0 = 15.0

Instance Main Methods:

// ✅ Main method può essere instance method!
class App {
    private String greeting = "Hello";

    void main() {
        println(greeting + ", Java 25!");
    }
}

Implicit imports:

Alcuni package sono automaticamente importati in compact source files:

  • java.io.*
  • java.lang.*
  • java.util.*

Module Import Declarations (JEP 511) ✅ Finalized

Importa tutti i package di un modulo in una riga!

❌ Prima - Import verbosi:

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpHeaders;
import java.net.http.HttpTimeoutException;
// ... altri 10 import dello stesso modulo

✅ Java 25 - Module import:

// Importa TUTTI i package del modulo java.net.http
import module java.net.http;

// Ora disponibili tutte le classi del modulo
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com"))
    .build();

Esempio reale:

import module java.sql;  // Tutto il modulo SQL

// Disponibili automaticamente:
// Connection, Statement, ResultSet, DriverManager, etc.

class DatabaseApp {
    void main() throws SQLException {
        Connection conn = DriverManager.getConnection(url);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM users");
    }
}

Vantaggi:

  • ✅ Meno linee di import
  • ✅ Non perdi mai un'import del modulo
  • ✅ Perfetto per prototyping rapido

Scoped Values (JEP 506) ✅ Finalized

Alternativa moderna a ThreadLocal per Virtual Threads!

❌ ThreadLocal - Problemi:

// ThreadLocal ha memory leaks e non scala con Virtual Threads
public class RequestContext {
    private static final ThreadLocal<User> currentUser = new ThreadLocal<>();

    public static void setUser(User user) {
        currentUser.set(user); // ❌ Devi ricordarti di rimuoverlo!
    }

    public static User getUser() {
        return currentUser.get();
    }

    public static void clear() {
        currentUser.remove(); // ❌ Spesso dimenticato!
    }
}

✅ Scoped Values - Immutable & Safe:

public class RequestContext {
    // Scoped Value - immutabile e scope-bound
    private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();

    public static User currentUser() {
        return CURRENT_USER.get();
    }

    public static void processRequest(User user, Runnable task) {
        // Valore disponibile solo nello scope
        ScopedValue.runWhere(CURRENT_USER, user, task);
        // Automaticamente rimosso fuori dallo scope!
    }
}

Uso:

User admin = new User("admin");

RequestContext.processRequest(admin, () -> {
    // currentUser() disponibile qui
    System.out.println("User: " + RequestContext.currentUser().name());

    // Anche nei child threads!
    Thread.ofVirtual().start(() -> {
        System.out.println("Child: " + RequestContext.currentUser().name());
    }).join();
});

// Fuori dallo scope - CURRENT_USER non più disponibile

Nested scopes:

ScopedValue<String> CONTEXT = ScopedValue.newInstance();

ScopedValue.runWhere(CONTEXT, "outer", () -> {
    println(CONTEXT.get()); // "outer"

    // Nested scope con valore diverso
    ScopedValue.runWhere(CONTEXT, "inner", () -> {
        println(CONTEXT.get()); // "inner"
    });

    println(CONTEXT.get()); // "outer" di nuovo
});

Vantaggi vs ThreadLocal:

  • Immutabile: Nessun rischio di modifica
  • Scope-bound: Auto-cleanup garantito
  • Virtual Thread friendly: Scala meglio
  • Memory efficient: Niente memory leaks

Stream Gatherers (JEP 485) ✅ Finalized

Custom intermediate operations per Stream API!

❌ Prima - Solo operazioni built-in:

// Per sliding window devi implementarlo manualmente
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// ❌ Non esiste .slidingWindow()

✅ Java 25 - Gatherers API:

import java.util.stream.Gatherers;

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

// ✅ Sliding window built-in!
List<List<Integer>> windows = numbers.stream()
    .gather(Gatherers.windowSliding(3))
    .toList();

// [[1,2,3], [2,3,4], [3,4,5]]

Built-in Gatherers:

// 1. Fixed Window
List.of(1,2,3,4,5,6).stream()
    .gather(Gatherers.windowFixed(2))
    .toList();
// [[1,2], [3,4], [5,6]]

// 2. Fold (accumulator custom)
String result = Stream.of("a", "b", "c")
    .gather(Gatherers.fold(() -> "", (acc, s) -> acc + s))
    .findFirst().orElse("");
// "abc"

// 3. Scan (accumulator che emette ogni step)
List<Integer> cumulative = List.of(1, 2, 3, 4).stream()
    .gather(Gatherers.scan(() -> 0, Integer::sum))
    .toList();
// [1, 3, 6, 10]

Custom Gatherer - Batch processor:

public class BatchGatherer<T> implements Gatherer<T, List<T>, List<T>> {
    private final int batchSize;

    public BatchGatherer(int batchSize) {
        this.batchSize = batchSize;
    }

    @Override
    public Supplier<List<T>> initializer() {
        return ArrayList::new;
    }

    @Override
    public Integrator<List<T>, T, List<T>> integrator() {
        return (state, element, downstream) -> {
            state.add(element);
            if (state.size() >= batchSize) {
                downstream.push(new ArrayList<>(state));
                state.clear();
            }
            return true;
        };
    }

    @Override
    public BiConsumer<List<T>, Downstream<? super List<T>>> finisher() {
        return (state, downstream) -> {
            if (!state.isEmpty()) {
                downstream.push(state);
            }
        };
    }
}

// Uso
List<List<Integer>> batches = IntStream.range(1, 11)
    .boxed()
    .gather(new BatchGatherer<>(3))
    .toList();
// [[1,2,3], [4,5,6], [7,8,9], [10]]

Real-world: Rate Limiter Gatherer

public record RateLimitGatherer<T>(Duration delay)
    implements Gatherer<T, AtomicLong, T> {

    @Override
    public Integrator<AtomicLong, T, T> integrator() {
        return (state, element, downstream) -> {
            long lastEmit = state.get();
            long now = System.nanoTime();
            long waitTime = delay.toNanos() - (now - lastEmit);

            if (waitTime > 0) {
                Thread.sleep(waitTime / 1_000_000);
            }

            state.set(System.nanoTime());
            downstream.push(element);
            return true;
        };
    }
}

// Emetti max 1 elemento al secondo
List<String> items = List.of("A", "B", "C", "D");
items.stream()
    .gather(new RateLimitGatherer<>(Duration.ofSeconds(1)))
    .forEach(System.out::println);

Primitive Types in Patterns (JEP 507) - 3rd Preview

Pattern matching ora supporta tipi primitivi!

// ✅ Pattern matching con primitivi
Object obj = 42;

switch (obj) {
    case int i when i > 0 -> println("Positive: " + i);
    case int i -> println("Non-positive: " + i);
    case double d -> println("Double: " + d);
    case String s -> println("String: " + s);
    default -> println("Other");
}

// instanceof con primitivi
if (obj instanceof int i) {
    println("It's an int: " + i);
}

// Numeric conversions automatici
Object value = 42L; // Long
if (value instanceof int i) {
    println(i); // Conversione Long -> int se safe
}

Structured Concurrency (JEP 505) - 5th Preview

Gestione strutturata di thread concorrenti!

// ✅ Structured concurrency
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

    // Lancia task paralleli
    Future<String> user = scope.fork(() -> fetchUser(userId));
    Future<List<Order>> orders = scope.fork(() -> fetchOrders(userId));
    Future<Account> account = scope.fork(() -> fetchAccount(userId));

    // Attendi tutti o fallisci al primo errore
    scope.join();
    scope.throwIfFailed();

    // Tutti i task completati con successo
    return new UserData(user.resultNow(), orders.resultNow(), account.resultNow());
}
// Scope chiuso - tutti i thread terminati automaticamente

ShutdownOnSuccess - Primo successo:

try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {

    // Lancia richieste verso più server
    scope.fork(() -> fetchFromServer1(query));
    scope.fork(() -> fetchFromServer2(query));
    scope.fork(() -> fetchFromServer3(query));

    // Attendi PRIMO successo e cancella gli altri
    scope.join();

    String result = scope.result();
    return result;
}

Class-File API (JEP 484) ✅ Finalized

API standard per leggere, scrivere e trasformare Java class files!

import java.lang.classfile.*;
import java.lang.classfile.instruction.*;

// Genera classe dinamicamente
byte[] classBytes = ClassFile.of().build(
    ClassDesc.of("com.example", "HelloWorld"),
    classBuilder -> {
        // Aggiungi constructor
        classBuilder.withMethod(
            "<init>",
            MethodTypeDesc.of(ConstantDescs.CD_void),
            ClassFile.ACC_PUBLIC,
            methodBuilder -> methodBuilder
                .withCode(codeBuilder -> codeBuilder
                    .aload(0)
                    .invokespecial(
                        ConstantDescs.CD_Object,
                        "<init>",
                        MethodTypeDesc.of(ConstantDescs.CD_void)
                    )
                    .return_()
                )
        );

        // Aggiungi main method
        classBuilder.withMethod(
            "main",
            MethodTypeDesc.of(
                ConstantDescs.CD_void,
                ConstantDescs.CD_String.arrayType()
            ),
            ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
            methodBuilder -> methodBuilder
                .withCode(codeBuilder -> codeBuilder
                    .getstatic(
                        ClassDesc.of("java.lang.System"),
                        "out",
                        ClassDesc.of("java.io.PrintStream")
                    )
                    .ldc("Hello from generated code!")
                    .invokevirtual(
                        ClassDesc.of("java.io.PrintStream"),
                        "println",
                        MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String)
                    )
                    .return_()
                )
        );
    }
);

// Salva .class file
Files.write(Path.of("HelloWorld.class"), classBytes);

🚀 Performance & Runtime

Compact Object Headers (Experimental)

Header degli oggetti più compatti per ridurre memory footprint:

# Abilita compact headers
java -XX:+UseCompactObjectHeaders MyApp

# Risultato: -15-20% heap memory usage

Vantaggi:

  • Meno memoria: 15-20% heap savings
  • Cache efficiency: Migliore utilizzo CPU cache
  • Throughput: +5-10% performance in memory-intensive apps

JFR Enhancements

JDK Flight Recorder con nuove capacità:

1. CPU-Time Profiling (JEP 509 - Experimental):

# Profiling CPU accurato su Linux
java -XX:+FlightRecorder \
     -XX:StartFlightRecording:settings=profile,filename=recording.jfr \
     -XX:+UnlockExperimentalVMOptions \
     -XX:+EnableJFRCPUProfiling \
     MyApp

2. Async Stack Walking (JEP 518):

Sampling stack walking senza bias di safepoint:

# Stack walking più accurato
java -XX:StartFlightRecording:settings=profile MyApp

3. Method Tracing via Bytecode (JEP 520):

Tracing metodi tramite bytecode instrumentation:

// Annota metodi da tracciare
@JFRTraced
public void businessLogic() {
    // JFR registra automaticamente timing
}

String::hashCode Optimization

Ottimizzazione constant folding per hash di stringhe costanti:

// Prima - hash calcolato a runtime
Map<String, User> cache = Map.of(
    "admin", adminUser,
    "guest", guestUser
);

// Java 25 - hash precalcolato a compile-time per chiavi statiche!
// +30% performance per Map lookups con chiavi costanti

Vector API (JEP 508) - 10th Incubation

SIMD operations per performance superiori:

import jdk.incubator.vector.*;

static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;

void vectorAdd(float[] a, float[] b, float[] result) {
    int i = 0;
    int upperBound = SPECIES.loopBound(a.length);

    // Vector operations - SIMD
    for (; i < upperBound; i += SPECIES.length()) {
        var va = FloatVector.fromArray(SPECIES, a, i);
        var vb = FloatVector.fromArray(SPECIES, b, i);
        var vc = va.add(vb);
        vc.intoArray(result, i);
    }

    // Tail - scalar fallback
    for (; i < a.length; i++) {
        result[i] = a[i] + b[i];
    }
}

Novità Java 25:

  • Float16 auto-vectorization su x64
  • Link a native math libs via FFM API
  • VectorShuffle supporta MemorySegment

🔐 Security & Cryptography

Post-Quantum Cryptography

Nuovi algoritmi PEM (JEP 470 - Preview):

// ML-KEM (FIPS 203) - Key Encapsulation
ML-KEM.EncapsulationKey pubKey = ...;
ML-KEM.KEM kem = ML-KEM.getInstance("ML-KEM-768");
ML-KEM.Encapsulated enc = kem.encapsulate(pubKey);
byte[] sharedSecret = enc.key();

// ML-DSA (FIPS 204) - Digital Signatures
ML-DSA.SigningKey privKey = ...;
ML-DSA.Signature sig = ML-DSA.getInstance("ML-DSA-65");
byte[] signature = sig.sign(privKey, message);

SHAKE MessageDigest Algorithms

// SHAKE128-256 e SHAKE256-512
MessageDigest shake128 = MessageDigest.getInstance("SHAKE128-256");
byte[] hash128 = shake128.digest(data);

MessageDigest shake256 = MessageDigest.getInstance("SHAKE256-512");
byte[] hash256 = shake256.digest(data);

🛠️ Tooling & Developer Experience

JShell Improvements

JShell ora supporta tutte le nuove feature di Java 25:

jshell --enable-preview

jshell> void main() { println("Hello!"); }
jshell> main()
Hello!

Deprecations & Removals

Rimosso:

  • Graal JIT Compiler (experimental) - Use GraalVM standalone

Deprecato:

  • ⚠️ sun.misc.Unsafe - Usa Foreign Function & Memory API

📊 Performance Benchmarks

Metrica Java 21 (LTS) Java 25 (LTS) Miglioramento
Startup Time 1.2s 0.8s -33%
Throughput 85K req/s 112K req/s +32%
Heap Memory (8GB app) 6.4 GB 5.1 GB -20%
GC Pause Time (G1) 42ms 28ms -33%
String hash (static) 850ns 590ns -31%
Vector ops (SIMD) 120ms 45ms -63%

🔄 Migration da Java 21

Update Java Version

# Verifica versione
java --version

# Download Java 25
# https://www.oracle.com/java/technologies/downloads/#java25

# Aggiorna JAVA_HOME
export JAVA_HOME=/path/to/jdk-25
export PATH=$JAVA_HOME/bin:$PATH

Gradle

// build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(25)
    }
}

// Abilita preview features se necessario
tasks.withType(JavaCompile) {
    options.compilerArgs += ['--enable-preview']
}

tasks.withType(Test) {
    jvmArgs += ['--enable-preview']
}

Maven

<!-- pom.xml -->
<properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <maven.compiler.release>25</maven.compiler.release>
</properties>

<!-- Preview features -->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <compilerArgs>
                    <arg>--enable-preview</arg>
                </compilerArgs>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <argLine>--enable-preview</argLine>
            </configuration>
        </plugin>
    </plugins>
</build>

IntelliJ IDEA

  1. File → Project Structure → Project SDK → Select Java 25
  2. Preferences → Build → Compiler → Java Compiler → Set "Project bytecode version" to 25
  3. Per preview features: Add --enable-preview to compiler options

💡 Quando Aggiornare?

✅ Aggiorna SUBITO se:

  • Vuoi supporto LTS lungo (8+ anni)
  • Hai app memory-intensive (compact headers)
  • Usi Virtual Threads (scoped values!)
  • Vuoi scrivere Java script (compact source files)
  • Serve post-quantum crypto

⚠️ Aspetta se:

  • Librerie critiche non supportano Java 25
  • Team non familiare con nuove feature
  • App legacy su Java 11/17 con migration complessa

🎯 Migration Roadmap:

  1. Q4 2025: Test in dev/staging
  2. Q1 2026: Gradual rollout production
  3. Q2 2026: Full adoption

🔗 Risorse Utili

🎓 Conclusioni

Java 25 è una release LTS fondamentale:

Flexible Constructors - Codice più sicuro e performante ✅ Compact Source Files - Java diventa linguaggio di scripting ✅ Scoped Values - Virtual Threads-friendly context sharing ✅ Stream Gatherers - Custom stream operations finalmente! ✅ Module Imports - Import semplificati per prototyping ✅ Performance - +32% throughput, -20% memory, -33% startup ✅ Post-Quantum Crypto - Sicurezza per decenni ✅ 8+ anni supporto - Stabilità enterprise garantita