Java 25 вийшла 16 вересня 2025 року, і більшість вендорів вважають її релізом із довгостроковою підтримкою (OpenJDK: JDK 25). Для enterprise-команд це сигнал планувати наступне оновлення платформи: саме LTS-версії роками працюють у продакшені. У статті зосереджуємося на тому, що реально змінюється для бекенд-сервісів, — обсяг пам'яті, старт і прогрівання, конкурентність, ергономіка мови, — і даємо практичний шлях оновлення з Java 17 чи 21, зокрема що перевірити в застосунках на Spring Boot.

Навіщо взагалі оновлюватися

Якщо ви на Java 17, ви пропустили два LTS-цикли покращень; якщо на 21 — стрибок менший, але все одно відчутний. Три причини, які ми чуємо від клієнтів найчастіше:

  • Нижчі витрати на інфраструктуру. Пам'ять — головний чинник розміру контейнерів і віртуальних машин, а Java 25 робить compact object headers повноцінною функцією.
  • Швидший старт і прогрівання. Важливо для автомасштабування, serverless і частих деплоїв у Kubernetes.
  • Строки підтримки. Фреймворки посувають мінімальні версії. Spring Boot 4 залишає мінімальною Java 17, але додає повноцінну підтримку Java 25 (блог Spring, 2025), а бібліотеки дедалі частіше розраховують на новіші JDK.

Java 25 одним поглядом

JDK 25 містить 18 JEP. Фінальні (не preview) можливості, найважливіші для бекенд-команд:

JEP Можливість Чому це важливо
519 Compact Object Headers Менші об'єкти, менше heap, менше збирань сміття
514 Ahead-of-Time Command-Line Ergonomics Простіше створення AOT-кешів для швидшого старту
515 Ahead-of-Time Method Profiling Швидше прогрівання завдяки профілям із тренувального запуску
521 Generational Shenandoah GC з короткими паузами і генераційною ефективністю
506 Scoped Values Безпечніша і дешевша альтернатива ThreadLocal, ідеальна з віртуальними потоками
513 Flexible Constructor Bodies Валідація та обчислення перед super(...)
511 Module Import Declarations import module java.base; тощо
512 Compact Source Files and Instance Main Methods Скрипти й невеликі утиліти без бойлерплейту
510 Key Derivation Function API Стандартний API для KDF на кшталт HKDF

Preview та incubator-можливості — structured concurrency (п'яте preview), stable values, примітивні типи в патернах, Vector API — варто пробувати, але preview API в продакшен-сервіси ми не беремо.

Пам'ять: compact object headers

Кожен Java-об'єкт має заголовок. JEP 519 робить compact object headers, що з'явилися експериментально в JDK 24, повноцінною функцією. Результати, наведені в JEP, суттєві: в одному сценарії бенчмарк SPECjbb2015 використав на 22% менше heap і на 8% менше CPU, а в іншому кількість збирань сміття скоротилася на 15% (JEP 519). JEP також зазначає, що цей формат заголовків уже використовували сотні продакшен-сервісів в Amazon.

За замовчуванням функцію не ввімкнено. Увімкніть явно і виміряйте:

java -XX:+UseCompactObjectHeaders -jar app.jar

Для сервісів із великою кількістю об'єктів — кешів, API з масою DTO, обробки подій — це одна з найдешевших оптимізацій: жодних змін у коді, а pod'и потенційно менші.

Старт і прогрівання: AOT-кеш

AOT-кеш із Project Leyden зберігає результати завантаження і лінкування класів, а з JDK 25 — ще й профілі методів із тренувального запуску. Наступний старт пропускає цю роботу, і JIT може оптимізувати одразу (JEP 514; JEP 515). У JDK 25 створення кешу скоротили до одного кроку:

# Тренувальний запуск: проганяємо застосунок, виходимо; JVM записує кеш
java -XX:AOTCacheOutput=app.aot -jar app.jar

# Продакшен-запуски використовують кеш
java -XX:AOTCache=app.aot -jar app.jar

Дві практичні примітки: тренувальний запуск має проганяти реалістичні шляхи коду (smoke-тест старту чи короткий навантажувальний тест), а однокроковий процес під час створення кешу потребує приблизно вдвічі більше пам'яті, ніж налаштований heap, як попереджає JEP. На відміну від нативних образів GraalVM, AOT-кеш зберігає повну JVM, JIT і динамічні можливості; підходи порівнюємо у статті GraalVM Native Image для Spring Boot.

Збирання сміття: generational Shenandoah

Генераційний режим Shenandoah став повноцінною функцією в JDK 25 (JEP 521). Він поєднує дуже короткі паузи Shenandoah з ефективністю окремого збирання молодих об'єктів. G1 залишається за замовчуванням і є правильним вибором для більшості сервісів; generational Shenandoah чи ZGC варто розглядати для чутливих до затримок сервісів із великим heap — і вирішувати за навантажувальними тестами, а не за бенчмарками з блогів.

Конкурентність: scoped values

Scoped values дозволяють методу передавати незмінні дані викликаним методам і дочірнім потокам із чітким часом життя (JEP 506). Вони дешевші й безпечніші за ThreadLocal, особливо з мільйонами віртуальних потоків, де thread-local змінні множать споживання пам'яті.

private static final ScopedValue<RequestContext> CONTEXT = ScopedValue.newInstance();

void handle(Request request) {
    ScopedValue.where(CONTEXT, RequestContext.from(request))
               .run(() -> orderService.process(request));
}

// будь-де нижче по стеку викликів, без передачі параметрів
RequestContext ctx = CONTEXT.get();

Самі віртуальні потоки з'явилися в Java 21; JDK 24 прибрав більшість випадків pinning у блоках synchronized, що зробило їх значно практичнішими. Про використання в продакшені — у статті Віртуальні потоки в Spring Boot.

Ергономіка мови

Гнучкі тіла конструкторів дозволяють інструкції перед super(...) чи this(...), якщо вони не звертаються до об'єкта, що конструюється (JEP 513). Валідація нарешті читається природно:

public class Money extends Amount {
    public Money(BigDecimal value, Currency currency) {
        if (value.signum() < 0) throw new IllegalArgumentException("Negative amount");
        Objects.requireNonNull(currency);
        super(value.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_EVEN));
        this.currency = currency;
    }
}

Імпорт модулів (import module java.sql;) і компактні вихідні файли з instance-методами main роблять написання невеликих утиліт і скриптів на Java приємним навіть без налаштування збірки (JEP 511; JEP 512).

План оновлення з Java 17 чи 21

  1. Інвентаризація. Перелічіть сервіси, їхні JDK, версії фреймворків та інструменти збірки. Перевірте, що Gradle, плагіни Maven, Lombok, Mockito, бібліотеки роботи з байт-кодом і агенти (APM, профайлери) підтримують Java 25.
  2. Спершу оновіть фреймворки. Перейдіть на Spring Boot 3.5 чи 4.x, Hibernate та інші залежності з офіційною підтримкою цільового JDK. Шлях Spring Boot 3.5 → 4 описано у статті Spring Boot + Kotlin у 2026.
  3. Зберіть новим JDK, прогоніть тести, виправте попередження. Типові проблеми між 17 і 25: видалені чи інкапсульовані внутрішні API, суворіші попередження про динамічне завантаження агентів і старі бібліотеки маніпуляції байт-кодом.
  4. Запустіть на staging під навантаженням. Порівняйте heap, CPU, паузи GC, старт і p99-затримку зі старою версією.
  5. Спробуйте безкоштовні виграші. Увімкніть compact object headers і AOT-кеш на одному сервісі та виміряйте.
  6. Розгортайте поступово, сервіс за сервісом, тримаючи старий образ для відкату.
  7. Оновіть базові образи і CI, щоб нові сервіси стартували на Java 25 за замовчуванням.

Проблеми оновлення, які трапляються найчастіше

Проблема Типовий симптом Рішення
Старі бібліотеки байт-коду (ASM, Byte Buddy, cglib) Помилки про непідтримувану версію class-файлу Оновити бібліотеку чи фреймворк, що її містить
Lombok чи annotation processors Помилки компіляції Оновити до версії з підтримкою нового JDK
Динамічне завантаження агентів Попередження під час старту від APM чи інструментів мокінгу Завантажувати агентів явно через -javaagent
Видалені внутрішні API IllegalAccessError, відсутні класи Замінити на публічні API; не використовувати --add-opens як постійне рішення
Налаштування пам'яті в контейнері Розмір heap не такий, як очікувалося Явно задати -XX:MaxRAMPercentage і виміряти знову

FAQ

Java 25 справді LTS? Більшість вендорів, зокрема Oracle, Eclipse Adoptium, Amazon Corretto та Azul, надають для неї довгострокову підтримку. Тривалість залежить від вендора.

Чи варто пропустити Java 21 і перейти одразу з 17 на 25? Так, якщо ваші фреймворки це підтримують. Зусилля на міграцію з 17 на 25 ненабагато більші, ніж на 21, а років підтримки ви отримуєте більше.

Чи робить Java 25 непотрібним GraalVM native image? Не повністю. AOT-кеш суттєво покращує старт, зберігаючи повну JVM; нативні образи все одно стартують швидше і споживають менше пам'яті, але обмежують динамічні можливості.

Чи можуть compact object headers щось зламати? Для Java-коду вони прозорі. Протестуйте нативні бібліотеки та інструменти, що аналізують розміщення об'єктів у пам'яті, і виміряйте ефект, перш ніж вмикати всюди.

Джерела

  1. OpenJDK. Сторінка проєкту JDK 25.
  2. OpenJDK. JEP 519: Compact Object Headers.
  3. OpenJDK. JEP 514: AOT Command-Line Ergonomics і JEP 515: AOT Method Profiling.
  4. OpenJDK. JEP 521: Generational Shenandoah.
  5. OpenJDK. JEP 506: Scoped Values, JEP 513: Flexible Constructor Bodies, JEP 511: Module Import Declarations, JEP 512: Compact Source Files.
  6. Spring (2025). Spring Boot 4.0.0 available now.