В одной из моих предыдущих статей я писал о фичах между LTS-версиями Java 17 и 21. Сегодня, два года спустя (Как?! Уже два года?!), выходит новый LTS-релиз — Java 25.
Подавляющее большинство проектов пропускают промежуточные релизы и используют только LTS-версии Java. Так что давайте посмотрим, какие возможности новая LTS-версия (Java 25) приносит по сравнению с предыдущей LTS-версией (Java 21).
В таблицах ниже я буду использовать следующие сокращения для обозначения состояния фич:
- exp = Experimental; 
- inc[2|3|4|etc] = Incubator [2|3|4|etc]; 
- pre[2|3|4|etc] = Preview [2|3|4|etc]; 
- prod = Production; 
- w/d = Withdrawn. 
Production-ready фичи в Java 25
| Feature | Java 21 | Java 22 | Java 23 | Java 24 | Java 25 | 
|---|---|---|---|---|---|
| Scoped Values | JEP 446 (pre) | JEP 464 (pre2) | JEP 481 (pre3) | JEP 487 (pre4) | JEP 506 (prod) | 
| Flexible Constructor Bodies | - | JEP 447 (pre) | JEP 482 (pre2) | JEP 492 (pre3) | JEP 513 (prod) | 
| Unnamed Variables & Patterns | JEP 443 (pre) | JEP 456 (prod) | + | + | + | 
| Stream Gatherers | - | JEP 461 (pre) | JEP 473 (pre2) | JEP 485 (prod) | + | 
| Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism | - | - | - | JEP 496 (prod) | + | 
| Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm | - | - | - | JEP 497 (prod) | + | 
| Key Derivation Function API | - | - | - | JEP 478 (pre) | JEP 510 (prod) | 
| Foreign Function & Memory API | JEP 442 (pre3) | JEP 454 (prod) | + | + | + | 
| Compact Source Files and Instance Main Methods | JEP 445 (pre) | JEP 463 (pre2) | JEP 477 (pre3) | JEP 495 (pre4) | JEP 512 (prod) | 
| Markdown Documentation Comments | - | - | JEP 467 (prod) | + | + | 
| Module Import Declarations | - | - | JEP 476 (pre) | JEP 494 (pre2) | JEP 511 (prod) | 
| Ahead-of-Time Class Loading & Linking | - | - | - | JEP 483 (prod) | + | 
| Ahead-of-Time Command-Line Ergonomics | - | - | - | - | JEP 514 (prod) | 
| Ahead-of-Time Method Profiling | - | - | - | - | JEP 515 (prod) | 
| JFR Cooperative Sampling | - | - | - | - | JEP 518 (prod) | 
| JFR Method Timing & Tracing | - | - | - | - | JEP 520 (prod) | 
| Compact Object Headers | - | - | - | JEP 450 (exp) | JEP 519 (prod) | 
| Prepare to Restrict the Use of JNI | - | - | - | JEP 472 (prod) | + | 
| Class-File API | - | JEP 457 (pre) | JEP 466 (pre2) | JEP 484 (prod) | + | 
| Permanently Disable the Security Manager | - | - | - | JEP 486 (prod) | + | 
| Launch Multi-File Source-Code Programs | - | JEP 458 (prod) | + | + | + | 
| Region Pinning for G1 | - | JEP 423 (prod) | + | + | + | 
| Late Barrier Expansion for G1 | - | - | - | JEP 475 (prod) | + | 
| ZGC: Generational Mode by Default | - | - | JEP 474 (prod) | + | + | 
| ZGC: Remove the Non-Generational Mode | - | - | - | JEP 490 (prod) | + | 
| Generational Shenandoah | - | - | - | JEP 404 (exp) | JEP 521 (prod) | 
| Synchronize Virtual Threads without Pinning | - | - | - | JEP 491 (prod) | + | 
| Linking Run-Time Images without JMODs | - | - | - | JEP 493 (prod) | + | 
| Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal | - | - | JEP 471 (prod) | + | + | 
| Warn upon Use of Memory-Access Methods in sun.misc.Unsafe | - | - | - | JEP 498 (prod) | + | 
| Remove the Windows 32-bit x86 Port | - | - | - | JEP 479 (prod) | + | 
| Deprecate the 32-bit x86 Port for Removal | - | - | - | JEP 501 (prod) | + | 
| Remove the 32-bit x86 Port | - | - | - | - | JEP 503 (prod) | 
Preview-фичи в Java 25
| Feature | Java 21 | Java 22 | Java 23 | Java 24 | Java 25 | 
|---|---|---|---|---|---|
| Stable Values | - | - | - | - | JEP 502 (pre) | 
| PEM Encodings of Cryptographic Objects | - | - | - | - | JEP 470 (pre) | 
| Structured Concurrency | JEP 453 (pre) | JEP 462 (pre2) | JEP 480 (pre3) | JEP 499 (pre4) | JEP 505 (pre5) | 
| Primitive Types in Patterns, instanceof, and switch | - | - | JEP 455 (pre) | JEP 488 (pre2) | JEP 507 (pre3) | 
Incubator фичи в Java 25
Экспериментальные фичи в Java 25
| Feature | Java 21 | Java 22 | Java 23 | Java 24 | Java 25 | 
|---|---|---|---|---|---|
| JFR CPU-Time Profiling | - | - | - | - | JEP 509 (exp) | 
Отозванные фичи между Java 21 и Java 25
Production-Ready фичи с примерами
Теперь давайте кратко разберём некоторые фичи, готовые к использованию в проде, и посмотрим примеры кода.
 Полный исходный код доступен на GitHub: pfilaretov42/java-features.
Scoped Values
Scoped Values предоставляют более безопасную альтернативу thread-local переменным, предлагая неизменяемые, наследуемые значения, доступные только внутри определённой области видимости.
До: подход с ThreadLocal
Словно ношение Единого Кольца — опасно, если доверить кому попало:
class ThreadLocalTest {
    private static final ThreadLocal<String> CURRENT_RING_BEARER = new ThreadLocal<>();
    void dangerousJourney() {
        CURRENT_RING_BEARER.set("Frodo");
        try {
            travelToMordor();
        } finally {
            CURRENT_RING_BEARER.remove();
        }
    }
    void travelToMordor() {
        // Any method in the call chain can access...
        String bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden");
        // ...and modify
        CURRENT_RING_BEARER.set("Sam");
        bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden");
    }
    public static void main() {
        ThreadLocalTest test = new ThreadLocalTest();
        test.dangerousJourney();
    }
}
После: способ со ScopedValue
Подобно фиалу Галадриэль — свет, надёжно удерживаемый внутри:
public class ScopedValuesTest {
    private static final ScopedValue<String> CURRENT_RING_BEARER = ScopedValue.newInstance();
    void safeJourney() {
        ScopedValue.where(CURRENT_RING_BEARER, "Frodo")
                .run(this::travelToMordorSafely);
    }
    private void travelToMordorSafely() {
        // Only accessible in this scope, cannot be modified
        String bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden safely");
        // Attempting to rebind would result in compilation error:
        //currentRingBearer.set("Sam");
    }
    void noJourney() {
        // Not accessible outside the scope, throws NoSuchElementException (ScopedValue not bound):
        String bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden");
    }
    public static void main() {
        ScopedValuesTest test = new ScopedValuesTest();
        test.safeJourney();
        test.noJourney();
    }
}
Flexible Constructor Bodies
Позволяет более гибко располагать инструкции в теле конструктора, включая возможность размещать код перед явными вызовами других конструкторов (this() или super()).
До: жёсткие правила конструктора
Словно непоколебимые стены Ортанка — никакой гибкости:
class Palantir {
    private final String owner;
    private final boolean isCorrupted;
    public Palantir(String owner) {
        // This would not compile:
        //validateOwner(owner);
        // Call to this() must be the first statement:
        this(validateOwner(owner), false);
    }
    private static String validateOwner(String owner) {
        if (owner == null || owner.isBlank()) {
            throw new IllegalArgumentException("Owner cannot be null or blank");
        }
        System.out.println("A new palantir is given to " + owner);
        return owner;
    }
    private Palantir(String owner, boolean isCorrupted) {
        this.owner = owner;
        this.isCorrupted = isCorrupted;
    }
}
После: изящная свобода конструктора
Теперь струится, как воды Бруинена — естественно и свободно:
class Palantir {
    private final String owner;
    private final boolean isCorrupted;
    
    public Palantir(String owner) {
        validateOwner(owner);
        // Now this() call is allowed after other statements:
        this(owner, false);
    }
    private static void validateOwner(String owner) {
        if (owner == null || owner.isBlank()) {
            throw new IllegalArgumentException("Owner cannot be null or blank");
        }
        System.out.println("A new palantir is given to " + owner);
    }
    private Palantir(String owner, boolean isCorrupted) {
        this.owner = owner;
        this.isCorrupted = isCorrupted;
    }
}
Unnamed Variables & Patterns
Позволяет разработчикам явно отмечать неиспользуемые переменные и шаблоны с помощью символа подчёркивания (_), чтобы повысить читаемость кода.
Типичные случаи использования:
- параметры исключений; 
- параметры лямбд; 
- переменные при pattern matching; 
- переменные в циклах, когда нужен только счётчик. 
До: приходилось давать имена неиспользуемым переменным
Как если бы у каждого орка из Мордора был бейджик с именем:
try {
    int rings = forgeNewRing();
} catch (RingForgingException e) {  // Never used
    System.out.println("The fires of Mount Doom failed us!");
}
// Pattern matching with unused bindings
if (fighter instanceof Elf(String name, Weapon(String type, int damage))) {  // name and damage unused
    System.out.println("Armed with: " + type);
}
После: лаконичное объявление неиспользуемых переменных
Так же элегантно, как Леголас, попирающий законы гравитации:
try {
    int _ = forgeNewRing();
} catch (RingForgingException _) {  // Clear this is unused
    System.out.println("The fires of Mount Doom failed us!");
}
// Clean pattern matching
if (fighter instanceof Elf(_, Weapon(String type, _))) {
    System.out.println("Armed with: " + type);
}
Stream Gatherers
Представляет пользовательские промежуточные операции с помощью нового метода gather(Gatherer), который позволяет выполнять более сложные преобразования стримов, например, оконные функции.
До: ограничено встроенными операциями
Как попытка выковать Единое Кольцо, имея лишь простые инструменты:
List<String> hobbits = List.of("Frodo", "Sam", "Merry", "Pippin");
// To get overlapping pairs, we needed a workarounds
List<String> pairs = IntStream.range(0, hobbits.size() - 1)
        .mapToObj(i -> hobbits.get(i) + " & " + hobbits.get(i + 1))
        .toList();
System.out.println(pairs);
// Output: ["Frodo & Sam", "Sam & Merry", "Merry & Pippin"]
После: пользовательские преобразования стримов
Теперь под рукой могущество эльфийских кузнецов:
List<String> hobbits = List.of("Frodo", "Sam", "Merry", "Pippin");
Gatherer<String, ?, String> pairing = Gatherer.ofSequential(
        () -> new Object() { String previous; },
        (state, element, downstream) -> {
            if (state.previous != null) {
                downstream.push(state.previous + " & " + element);
            }
            state.previous = element;
            return true;
        }
);
List<String> pairs = hobbits.stream()
        .gather(pairing)
        .toList();
System.out.println(pairs);
// Output: ["Frodo & Sam", "Sam & Merry", "Merry & Pippin"]
Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism
Java 24 представляет встроенную поддержку механизма инкапсуляции ключей на основе модульных решёток (ML-KEM), который является частью квантово-устойчивой криптографии.
Эта фича обеспечивает квантово-устойчивую инкапсуляцию ключей, используемую для защиты симметричных ключей при передаче по небезопасным каналам связи с применением криптографии с открытым ключом.
Её можно использовать, чтобы противостоять мощи квантовых компьютеров — будущей угрозе для классических RSA и Диффи–Хеллмана.
На примере обмена сессионными ключами...
// Elrond prepares his runes of protection (Receiver creates key pair)
ElrondTheReceiver elrond = new ElrondTheReceiver();
// Gandalf crafts a secret using Elrond’s rune (Sender uses receiver's public key to encapsulate session key)
GandalfTheSender gandalf = new GandalfTheSender(elrond.revealPublicRune());
SecretKey senderSessionKey = gandalf.getSessionKey();
// Elrond deciphers the sealed whisper from Gandalf (Receiver decapsulates to get the same session key)
SecretKey receiverSessionKey = elrond.decapsulateWhisper(gandalf.getSealedWhisper());
boolean secretsMatch = MessageDigest.isEqual(senderSessionKey.getEncoded(), receiverSessionKey.getEncoded());
// Output for verification:
HexFormat hex = HexFormat.of();
System.out.println("Sender session key:   " + hex.formatHex(senderSessionKey.getEncoded()));
System.out.println("Receiver session key: " + hex.formatHex(receiverSessionKey.getEncoded()));
System.out.println("Secrets match: " + secretsMatch);
if (secretsMatch) {
    // Gandalf and Elrond exchange messages using the securely transmitted session key
    // ...
}
...мы видим следующее.
До: передача ключей с помощью RSA
Словно держать в руках сталь Гондора — мощно, но не всесильно:
/**
 * Receiver generates key pair and decapsulates session key
 */
class ElrondTheReceiver {
    private final KeyPair keyPair;
    public ElrondTheReceiver() throws GeneralSecurityException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(4096);
        keyPair = generator.generateKeyPair();
    }
    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }
    public SecretKey decapsulateWhisper(byte[] sealedWhisper) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
        byte[] keyBytes = cipher.doFinal(sealedWhisper);
        return new SecretKeySpec(keyBytes, "AES");
    }
}
/**
 * Sender uses receiver's public key to generate session key
 */
class GandalfTheSender {
    private final SecretKey sessionKey; // Sender’s secret session key
    private final byte[] sealedWhisper; // The message with session key encapsulated for Receiver
    public GandalfTheSender(PublicKey receiverPublicKey) throws GeneralSecurityException {
        // Generate session key (AES)
        KeyGenerator generator = KeyGenerator.getInstance("AES");
        generator.init(256);
        sessionKey = generator.generateKey();
        // Encrypt (encapsulate) session key with RSA
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, receiverPublicKey);
        sealedWhisper = cipher.doFinal(sessionKey.getEncoded());
    }
    public SecretKey getSessionKey() {
        return sessionKey;
    }
    public byte[] getSealedWhisper() {
        return sealedWhisper;
    }
}
После: постквантовая инкапсуляция ключей
Подобно Вратам Мории — неприступны для любой силы:
/**
 * Receiver generates key pair and decapsulates session key
 */
class ElrondTheReceiver {
    private final KeyPair keyPair;
    public ElrondTheReceiver() throws GeneralSecurityException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("ML-KEM");
        keyPair = generator.generateKeyPair();
    }
    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }
    public SecretKey decapsulateWhisper(byte[] sealedWhisper) throws GeneralSecurityException {
        KEM kem = KEM.getInstance("ML-KEM");
        KEM.Decapsulator decapsulator = kem.newDecapsulator(keyPair.getPrivate());
        return decapsulator.decapsulate(sealedWhisper);
    }
}
/**
 * Sender uses receiver's public key to generate session key
 */
class GandalfTheSender {
    private final SecretKey sessionKey; // Sender’s secret session key
    private final byte[] sealedWhisper; // The message with session key encapsulated for Receiver
    public GandalfTheSender(PublicKey receiverPublicKey) throws GeneralSecurityException {
        KEM kem = KEM.getInstance("ML-KEM");
        KEM.Encapsulator encapsulator = kem.newEncapsulator(receiverPublicKey);
        KEM.Encapsulated encapsulated = encapsulator.encapsulate();
        sessionKey = encapsulated.key();
        sealedWhisper = encapsulated.encapsulation();
    }
    public SecretKey getSessionKey() {
        return sessionKey;
    }
    public byte[] getSealedWhisper() {
        return sealedWhisper;
    }
}
Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm
ML-DSA обеспечивает постквантовые защищённые цифровые подписи на основе криптографии на решётках и является частью нового криптографического набора Java для эпохи квантовых вычислений.
На примере подписи сообщений...
// Gandalf prepares his spell (Sender creates a public/private key pair)
GandalfTheSender gandalf = new GandalfTheSender();
// Sender signs a message using the private key
String scroll = gandalf.speakWordsOfPower();
byte[] waxSeal = gandalf.signMessage(scroll);
// Aragorn receives the scroll and the seal
AragornTheReceiver aragorn = new AragornTheReceiver(gandalf.revealPublicRune());
// Verifying the true words of Gandalf (Receiver verifies the message using the sender's public key)
boolean isTrueScroll = aragorn.verifyMessage(scroll, waxSeal);
System.out.println("Is the scroll valid: " + isTrueScroll); // true
// Attempt to fool the ranger with a forged scroll (verification fails for counterfeit message)
String fakeScroll = """
    A new Power is rising. Against it the old allies and policies will not avail us at all. \
    There is no hope left in Elves or dying Númenor.
    """;
isTrueScroll = aragorn.verifyMessage(fakeScroll, waxSeal);
System.out.println("Is the forged scroll valid: " + isTrueScroll); // false
...мы видим следующее.
До: подпись с помощью ECDSA
Словно стены Изенгарда перед гневом энтов:
/**
 * Sender signs message with private key
 */
class GandalfTheSender {
    private final KeyPair keyPair;
    public GandalfTheSender() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
        generator.initialize(new ECGenParameterSpec("secp256r1"));
        keyPair = generator.generateKeyPair();
    }
    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }
    public byte[] signMessage(String message) throws Exception {
        Signature runeEngraver = Signature.getInstance("SHA256withECDSA");
        runeEngraver.initSign(keyPair.getPrivate());
        runeEngraver.update(message.getBytes());
        return runeEngraver.sign();
    }
    public String speakWordsOfPower() {
        return """
            It is not our part here to take thought only for a season, or for a few lives of Men, \
            or for a passing age of the world. We should seek a final end of this menace, \
            even if we do not hope to make one.
            """;
    }
}
/**
 * Receiver verifies the message with public key
 */
class AragornTheReceiver {
    private final PublicKey senderPublicKey;
    public AragornTheReceiver(PublicKey senderPublicKey) {
        this.senderPublicKey = senderPublicKey;
    }
    public boolean verifyMessage(String message, byte[] signature) throws Exception {
        Signature runeChecker = Signature.getInstance("SHA256withECDSA");
        runeChecker.initVerify(senderPublicKey);
        runeChecker.update(message.getBytes());
        return runeChecker.verify(signature);
    }
}
После: подпись с помощью постквантового ML-DSA
Словно оставляешь свой след в вечных залах Валинора:
/**
 * Sender signs message with private key
 */
class GandalfTheSender {
    private final KeyPair keyPair;
    public GandalfTheSender() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("ML-DSA");
        keyPair = generator.generateKeyPair();
    }
    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }
    public byte[] signMessage(String message) throws Exception {
        Signature runeEngraver = Signature.getInstance("ML-DSA");
        runeEngraver.initSign(keyPair.getPrivate());
        runeEngraver.update(message.getBytes());
        return runeEngraver.sign();
    }
    public String speakWordsOfPower() {
        return """
            It is not our part here to take thought only for a season, or for a few lives of Men, \
            or for a passing age of the world. We should seek a final end of this menace, \
            even if we do not hope to make one.
            """;
    }
}
/**
 * Receiver verifies the message with public key
 */
class AragornTheReceiver {
    private final PublicKey senderPublicKey;
    public AragornTheReceiver(PublicKey senderPublicKey) {
        this.senderPublicKey = senderPublicKey;
    }
    public boolean verifyMessage(String message, byte[] signature) throws Exception {
        Signature runeChecker = Signature.getInstance("ML-DSA");
        runeChecker.initVerify(senderPublicKey);
        runeChecker.update(message.getBytes());
        return runeChecker.verify(signature);
    }
}
Key Derivation Function API
Эта фича представляет стандартизированный API для функций формирования ключей (KDF), заменяя разрозненные реализации единым интерфейсом для безопасного получения ключей.
До: ручное получение ключей
Как выковать клинок в грубых кузницах Мордора:
String password = "Mellon!";
byte[] salt = generateSalt();
int iterations = 65_536;
int keyLengthBits = 256;
// derive the key
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLengthBits);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey key = factory.generateSecret(spec);
System.out.println("Derived key: " + HexFormat.of().formatHex(key.getEncoded()));
После: API для генерации ключей
Теперь, подобно кузнецам Эрегиона, мы создаём ключи с высшей точностью:
KDF hkdf = KDF.getInstance("HKDF-SHA256");
byte[] password = "Mellon!".getBytes();
byte[] salt = generateSalt();
byte[] info = "Say 'Friend' and enter".getBytes();
int keyLengthBytes = 32;
// derive the key
AlgorithmParameterSpec params = HKDFParameterSpec.ofExtract()
        .addIKM(password)
        .addSalt(salt)
        .thenExpand(info, keyLengthBytes);
SecretKey key = hkdf.deriveKey("AES", params);
System.out.println("Derived key: " + HexFormat.of().formatHex(key.getEncoded()));
Foreign Function & Memory API
Заменяет Java Native Interface (JNI) более безопасным и эффективным способом вызова нативного кода и работы с off-heap памятью.
Для простоты я опущу названия пакетов и длинные пути к файлам. Полностью рабочий пример доступен на GitHub.
До: опасный подход через JNI
Словно Чёрная Речь Мордора — могущественно, но опасно...
Создаём класс ElvenScroll:
public class ElvenScroll {
    static {
        // Load the native library forged in C
        System.loadLibrary("mordor");
    }
    // Declare the native spell that reads the length of ancient text
    public native int countRunes(String ancientText);
    public static void main() {
        ElvenScroll scroll = new ElvenScroll();
        String runes = "Speak, friend, and enter";
        int length = scroll.countRunes(runes);
        System.out.println("Runes counted: " + length);
    }
}
Компилируем класс ElvenScroll с опцией -h:
javac -h . ElvenScroll.java
На выходе получается заголовочный файл ElvenScroll.h.
Создаём C-файл:
#include <jni.h>
#include <string.h>
#include "ElvenScroll.h"
JNIEXPORT jint JNICALL Java_ElvenScroll_countRunes(JNIEnv *env, jobject obj, jstring ancientText) {
    // Convert Elvish runes to C-compatible form
    const char *runes = (*env)->GetStringUTFChars(env, ancientText, NULL);
    if (runes == NULL) return 0;
    // Count the runes
    int length = (int)strlen(runes);
    // Release the spellbound memory
    (*env)->ReleaseStringUTFChars(env, ancientText, runes);
    return length;
}
Собираем C-библиотеку (команда ниже — для macOS):
gcc -shared -fpic -o libmordor.dylib \
-I ${JAVA_HOME}/include \
-I ${JAVA_HOME}/include/darwin \
mordor.c
На выходе получается файл libmordor.dylib.
Теперь запускаем класс ElvenScroll.
После: безопасный FFM API
Словно эльфийские мосты Лотлориэна — изящно и надёжно:
public class ElvenScroll {
    public static void main() throws Throwable {
        // Get a linker and a lookup object
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdlib = linker.defaultLookup();
        // Get a handle to the foreign function ('strlen' from the C standard library)
        MethodHandle strlen = linker.downcallHandle(
            stdlib.find("strlen").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
        );
        try (Arena arena = Arena.ofConfined()) {
            // Allocate off-heap memory
            String runes = "Speak, friend, and enter";
            MemorySegment cString = arena.allocateFrom(runes);
            // Call the foreign function
            long runeCount = (long) strlen.invoke(cString);
            System.out.println("Runes counted: " + runeCount);
        }
        // Memory automatically freed here - no memory leaks!
        // No native code!
    }
}
Compact Source Files And Instance Main Methods
Упрощает синтаксис Java для начинающих, позволяя создавать однофайловые программы без шаблонного кода класса и предлагая более гибкие варианты объявления метода main().
До: многословное заклинание
Словно пространные и мудрёные речи Совета Элронда:
package dev.pfilaretov42.java25.csf_imm;
public class RingQuestBefore {
    public static void main(String[] args) {
        System.out.println("One does not simply walk into Mordor...");
    }
}
После: версия размером с хоббита
Словно короткое путешествие Бильбо:
// no package declaration
// no imports for classes in java.base module
void main() {
    IO.println("One does not simply walk into Mordor...");
}
Или с использованием instance-метода:
package dev.pfilaretov42.java25.csf_imm;
public class RingQuestAfterInstance {
    void main() {
        IO.println("One does not simply walk into Mordor...");
    }
}
Markdown Documentation Comments
Наконец-то! ? В Java появилась поддержка синтаксиса Markdown в комментариях Javadoc, что обеспечивает более удобочитаемую и поддерживаемую альтернативу HTML-тегам.
До: HTML-способ
Путь древних — громоздкий и тяжёлый, словно кузницы Изенгарда:
/**
* <h1>The One Ring</h1>
* <p>Forged by Sauron in the fires of Mount Doom.</p>
* <pre>{@code
* if (ring.isFound()) {
*     frodo.destroy(ring);
* }
* }</pre>
* <p><b>Warning:</b> Do not wear for extended periods.</p>
*/
public class OneRing {
  // ...
}
После: синтаксис Markdown
Теперь слова льются, словно эльфийская письменность Лотлориэна — аккуратно, красиво и точно:
/// # The One Ring
/// Forged by Sauron in the fires of Mount Doom.
/// ```java
/// if (ring.isFound()) {
///     frodo.destroy(ring);
/// }
/// ```
/// **Warning:** Do not wear for extended periods.
public class OneRing {
  // ...
}
Module Import Declarations
Вводит синтаксис import module, позволяющий разработчикам импортировать целые модули в обычных Java-файлах.
Используя следующие классы...
package lothlorien;
public class Galadriel {
    public void speakLightOfEärendil() {
        // ...
    }
}
package lothlorien;
public class Celeborn {
    public void offerCounsel() {
        // ...
    }
}
package lothlorien;
public class Haldir {
    public void escort() {
        // ...
    }
}
...и файл module-info.java...
module elves.of.lothlorien {
    exports lothlorien;
}
...вот что мы имеем.
До: объявления import
Словно бесконечные родословные Дома Финвэ:
import lothlorien.Celeborn;
import lothlorien.Galadriel;
import lothlorien.Haldir;
public class FarewellToLórien {
    void main() {
        Haldir guardian = new Haldir();
        guardian.escort();
      
        Celeborn lord = new Celeborn();
        lord.offerCounsel();
        Galadriel lady = new Galadriel();
        lady.speakLightOfEärendil();
    }
}
После: импорт модулей
Теперь всё организовано, словно в гномьем дворце:
import module elves.of.lothlorien;
public class FarewellToLórien {
    void main() {
        Haldir guardian = new Haldir();
        guardian.escort();
      
        Celeborn lord = new Celeborn();
        lord.offerCounsel();
        Galadriel lady = new Galadriel();
        lady.speakLightOfEärendil();
    }
}
Заключение
Всё ещё думаете, что за 30 лет Java устарела и стала скучной? Тогда взгляните на последнюю LTS-версию Java 25 — она приносит с собой множество классных фич!
 
           
 
snuk182
Наконец-то можно писать код на Си на Java!