Что нового в Java 26
Рассмотрим фичи Java 26 и поймём куда движется платформа.
В JDK 26 доступны 10 JEP, охватывающих язык, производительность и сборку мусора.
Скачать Axiom JDK 26 вы сможете в личном кабинете после его официального релиза.
Содержание
Как использовать фичи в предварительной и экспериментальной версиях
Новые
JEP 500: Подготовка к тому, что final будет final
JEP 517: HTTP/3 для HTTP Client API
JEP 522: G1 GC: повышение производительности приложений
Улучшенные
JEP 516: Ahead-of-Time Object Caching с любым GC
JEP 524: PEM-представление криптографических объектов (вторая предварительная версия)
JEP 525: Structured Concurrency (шестая предварительная версия)
JEP 526: Lazy Constants (вторая предварительная версия)
JEP 529: Vector API (одиннадцатый инкубатор)
JEP 530: Примитивные типы в patterns, instanceof и switch (четвёртая предварительная версия)
Удалённые
JEP 504: Удаление Applet API
Заключение
Чтобы попробовать экспериментальные фичи или фичи в предварительной версии, нужно их явно включить. Вы можете это сделать как через командную строку, так и настроить в интегрированной среде разработки (IDE). Некоторые фичи можно включать на уровне javac, а не только при запуске java.
В командной строке включите фичу в предварительной версии одним из следующих способов:
- Скомпилируйте программу с помощью javac --release 26 --enable-preview Main.java и запустите ее с помощью java --enable-preview Main.
- При использовании source code launcher запустите программу с java --enable-preview Main.java.
- При использовании jshell запустите его с помощью jshell --enable-preview.
JEP 500: Подготовка к тому, что final будет final
JEP 500 подготавливает экосистему к будущему релизу Java, где изменение final-полей через глубокую рефлексию (deep reflection) по умолчанию будет запрещено. В перспективе вместо предупреждений появятся исключения.
Как используются методы setAccessible() и set() класса java.lang.reflect.Field для изменения final-поля:
// Обычный класс с final-полем
class C {
final int x;
C() { x = 100; }
}
// 1. Получаем доступ к final-полю через глубокую рефлексию
java.lang.reflect.Field f = C.class.getDeclaredField("x");
f.setAccessible(true); // Делаем final-поле изменяемым
// 2. Создаём объект
C obj = new C();
System.out.println(obj.x); // Напечатает 100
// 3. Меняем final-поле в объекте
f.set(obj, 200);
System.out.println(obj.x); // Напечатает 200
f.set(obj, 300);
System.out.println(obj.x); // Напечатает 300
Теперь при попытке изменения поля final через рефлексию вы будете видеть следующее предупреждение:
WARNING: Final field f in p.C has been [mutated/unreflected for mutation] by class com.foo.Bar.caller in module N (file:/path/to/foo.jar)
WARNING: Use --enable-final-field-mutation=N to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
Чтобы избавиться от таких предупреждений, используйте новую опцию командной строки --enable-final-field-mutation. С её помощью перечисляются модули, которым разрешено изменение final-полей:
- Для classpath-кода:
java --enable-final-field-mutation=ALL-UNNAMED ...
- Для конкретных модулей:
java --enable-final-field-mutation=M1,M2 ...
Но этого недостаточно: пакет с целевым полем должен быть открыт для кода, который использует рефлексию.
Чтобы управлять поведением Java при несанкционированном изменении final-полей, используйте опцию --illegal-final-field-mutation, которая может принимать одно из четырёх значений:
- allow — разрешает изменений final-полей без предупреждений.
--illegal-final-field-mutation=allow
- warn — разрешает изменений final-полей, но выдаётся предупреждение при первом выполнении модуля такой несанкционированной операции. Это поведение по умолчанию в Java 26.
--illegal-final-field-mutation=warn
- debug — похоже на warn, но предупреждение выдаётся при каждой несанкционированной операции.
--illegal-final-field-mutation=debug
- deny — запрещает изменений final-полей. При несанкционированной операции возвращается IllegalAccessException. В одном из будущих релизов этот режим станет режимом по умолчанию.
--illegal-final-field-mutation=deny
Когда режимом по умолчанию станет deny, вариант allow будет удалён, а warn и debug будут поддерживаться как минимум ещё в течение одного релиза.
Чтобы заранее подготовиться к этим изменениям, рекомендуется уже сейчас запускать существующий код в режиме deny, чтобы выявить места, где final- поля изменяются через глубокую рефлексию.
JVM охотнее оптимизирует код, если может доверять неизменяемости полей. Например, для final-полей возможны оптимизации вроде constant folding и последующие более агрессивные оптимизации.
Если же платформа допускает, что любое поле final можно в любой момент переписать через рефлексию, то JVM вынуждена быть осторожнее.
JEP 500 преследует не только безопасность и целостность, но и потенциальный выигрыш по производительности.
JEP 517: HTTP/3 для HTTP Client API
JEP 321 (JDK 11) добавил в Java Platform HTTP Client API. Он поддерживает HTTP/1.1 и HTTP/2 и изначально проектировался так, чтобы новые версии протокола можно было добавлять с минимальными изменениями. По умолчанию API предпочитает HTTP/2, но прозрачно откатывается на HTTP/1.1, если целевой сервер HTTP/2 не поддерживает.
В JEP 517 предлагается обновить HTTP Client API так, чтобы он поддерживал протокол HTTP/3, и библиотеки с приложениями могли работать с HTTP/3-серверами с минимальными изменениями в коде.
HTTP/3, пришедший на смену HTTP/2, был стандартизован в 2022 году Internet Engineering Task Force (IETF). В HTTP/3 вместо TCP используется транспортный протокол QUIC.
Поддержка HTTP/3 позволит приложениям, использующим HTTP Client API, воспользоваться преимуществами HTTP/3, среди которых:
- потенциально более быстрый handshake;
- снижение влияния сетевых проблем вроде head-of-line blocking;
- более надёжная передача данных, особенно в средах с высокой потерей пакетов.
HTTP/3 уже поддерживается большинством браузеров и используется примерно на трети всех сайтов.
Чтобы отправить запрос по HTTP/3, нужно явно включить этот режим. Это можно сделать, указав версию протокола HTTP_3 у объекта HttpClient. Тогда все запросы, отправленные через этот клиент, по умолчанию будут предпочитать HTTP/3:
var client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
Либо можно задать предпочтительную версию протокола для конкретного HttpRequest:
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.version(HttpClient.Version.HTTP_3)
.GET().build();
Больше никаких изменений не требуется. После того как HTTP/3 выбран в качестве предпочтительного либо у запроса, либо у клиента, то запрос отправляется обычным способом. Если целевой сервер не поддерживает HTTP/3, то запрос по умолчанию переключится на HTTP/2 или даже на HTTP/1.1, в зависимости от ситуации.
JEP 522: G1 GC: повышение производительности приложений
JEP 522 направлен на повышение общей производительности приложений, использующих G1 GC, за счет уменьшения накладных расходов G1 на синхронизацию.
G1 GC отслеживает объектные ссылки, чтобы после перемещения объектов эффективно обновлять их. Но некоторые приложения меняют ссылки настолько часто, что G1 приходится подключать фоновые потоки-оптимизаторы, а им, в свою очередь, нужно синхронизироваться с потоками приложения, чтобы не мешать друг другу. Именно эта координация замедляет write barriers в G1.
JEP решает эту проблему так: потоки приложения и потоки-оптимизаторы GC работают с разными копиями служебных данных отслеживания и при необходимости переключаются между ними. Это должно повысить общую производительность без изменений в том, как пользователи работают с G1.
JEP 516: Ahead-of-Time Object Caching с любым GC
JEP 516 дополнительно развивает механизм AOT сache, появившийся в JDK 24 в рамках интеграции Project Leyden. AOT сache позволяет сократить время старта и прогрева Java-приложений, создавая кэш уже загруженных и связанных классов, а также профилей методов. Раньше этот кэш был несовместим с ZGC — низколатентным сборщиком мусора для Java.
Теперь AOT Cache можно использовать с любым сборщиком мусора, включая ZGC, так что больше не нужно выбирать между низкими задержками и быстрым стартом приложения.
// тренировочный запуск приложения
java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App
// запуск приложения в продакшн
java -XX:AOTCache=app.aot -cp app.jar com.example.App
JEP 524: PEM-представление криптографических объектов (вторая предварительная версия)
Эта возможность вводит API для кодирования криптографических ключей, сертификатов и списков отзыва сертификатов в PEM-формат и для обратного преобразования PEM в объектное представление. В JDK 26 функциональность повторно выходит в предварительной версии с рядом изменений, включая переименование некоторых классов и методов.
JEP 525: Structured Concurrency (шестая предварительная версия)
Structured Concurrency появилась в JDK 19 и делает параллельный код более понятным и управляемым. Смысл в том, что время жизни подзадач привязано к конкретной области выполнения. Ошибки, отмена и наблюдаемость при этом подчиняются явной родительско-дочерней структуре: если одна подзадача завершается с ошибкой, остальные можно автоматически отменить.
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
record Response(String user, int order) {}
class Service {
Response handle() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(this::findUser);
Subtask<Integer> order = scope.fork(this::fetchOrder);
scope.join(); // Дожидаемся подзадач, исключения пробрасываются наверх
// Обе подзадачи успешно завершились — собираем итоговый результат
return new Response(user.get(), order.get());
}
}
}
Изменения в JDK 26:
- Новый метод onTimeout() в интерфейсе Joiner позволяет его реализациям возвращать результат при истечении таймаута.
- Joiner::allSuccessfulOrThrow() теперь возвращает список результатов, а не поток подзадач.
- Joiner::anySuccessfulResultOrThrow() теперь называется anySuccessfulOrThrow().
- Статический метод open(), который принимает Joiner и функцию для изменения конфигурации по умолчанию, теперь принимает UnaryOperator вместо Function.
JEP 526: Lazy Constants (вторая предварительная версия)
Впервые Lazy Constants появились в JDK 25 под названием Stable Values. В JEP 526 они получили новое имя, а их назначение сместилось в сторону более высокоуровневых сценариев использования: низкоуровневые методы при этом убрали.
Lazy constants — это объекты, которые хранят неизменяемые данные. Как и final-поля, JVM может рассматривать их как константы и применять к ним те же оптимизации. Но в отличие от final, их необязательно инициализировать сразу в момент создания. Благодаря этому приложение может инициализировать такие значения постепенно и быстрее стартовать.
Кроме того, для ленивых коллекций теперь доступны вспомогательные методы List.ofLazy и Map.ofLazy. Также убрали несколько дополнительных фабричных методов, а вычисляемые значения null больше не допускаются.
import java.lang.LazyConstant;
import java.util.List;
public class LazyConstantsDemo {
// LazyConstant хранится в поле final для лучшей оптимизации со стороны JVM
private static final LazyConstant<Logger> LOGGER =
LazyConstant.of(() -> Logger.create(LazyConstantsDemo.class));
// Каждый элемент инициализируется при первом обращении
private static final List<Worker> workers =
List.ofLazy(3, i -> new Worker("worker-" + i));
public static void main(String[] args) {
System.out.println("App started (without eagerly creating everything).");
// Первое обращение запускает инициализацию элемента с индексом 0
workers.get(0).doWork();
// Повторное обращение использует тот же уже инициализированный элемент
workers.get(0).doWork();
// Логгер тоже инициализируется при первом использовании
LOGGER.get().info("Lazy constants are doing their job.");
}
static final class Worker {
private final String name;
Worker(String name) { this.name = name; }
void doWork() {
System.out.println(name + " working");
}
}
}
JEP 529: Vector API (одиннадцатый инкубатор)
Vector API появилось в JDK 16 (JEP 388) в модуле jdk.incubator.vector и всё ещё остаётся в инкубационном периоде. В одиннадцатом инкубаторе без изменений.
Vector API нужен для векторных вычислений, которые во время выполнения компилируются в оптимальные SIMD-инструкции на конкретной платформе.
Vector API останется в инкубаторе тех пор, пока необходимые возможности Project Valhalla не будут доступны в превью. Как только это произойдет, Vector API перейдёт из инкубатора в статус превью.
JEP 530: Примитивные типы в patterns, instanceof и switch (четвёртая предварительная версия)
Примитивные типы в patterns, instanceof и switch были представлены в JDK 23 (JEP 455) в предварительной версии, остались в предварительной версии без изменений в JDK 24 (JEP 488) и JDK 25 (JEP 507). В JDK 26 предлагается в четвёртый раз выпустить фичу в предварительной версии с двумя изменениями.
Во-первых, JEP 530 ужесточает правила, по которым компилятор определяет, гарантированно ли преобразование не приводит к потере данных. Теперь эта возможность явно охватывает и всегда безопасные преобразования типов, например byte -> int и int -> double, и константы, которые тоже можно безопасно преобразовать, например литерал 42 в byte.
Второе изменение — более строгие проверки dominance для примитивных типов в switch. Это значит, что switch теперь отклоняет такие case, до которых выполнение никогда не дойдет, потому что более ранний case уже перехватывает все значения, которые могла бы обработать следующая ветка.
int j = 16_777_216;
String s = switch (j) {
case float f -> {};
case 16_777_216 -> {}; // ошибка: case перекрыт,
// потому что 16_777_216
// точно представим как float
default -> {};
};
Кроме того, безусловный паттерн теперь всегда доминирует над любыми другими следующими паттернами:
int x = ...;
switch (x) {
case int _ -> {} // безусловный паттерн
case float _ -> {} // ошибка: case перекрыт
}
Конструкция switch расширяется на оставшиеся примитивные типы: long, float, double и boolean, а также на соответствующие им обёртки.
Если выражение в switch имеет тип long, float, double или boolean, константы в case должны быть того же типа либо соответствующего им типа обёртки. Например, для float или Float это должны быть именно литералы типа float. Такое ограничение введено неслучайно: если тип константы не совпадает с типом выражения в switch, могут возникать неявные преобразования с потерей данных, а поведение кода станет менее очевидным для разработчика. Поэтому следующий switch не скомпилировался, если бы литерал 0f по ошибке записали как 0:
float v = ...
switch (v) {
case 0f -> 5f;
case float x when x == 1f -> 6f + x;
case float x -> 7f + x;
}
JEP 504: Удаление Applet API
JEP 504 окончательно убирает Applet API: пакет java.applet, связанные с апплетами классы и оставшиеся элементы платформы, которые на него ссылались:
- java.applet.Applet,
- java.applet.AppletContext,
- java.applet.AppletStub,
- java.applet.AudioClip,
- java.beans.AppletInitializer,
- javax.swing.JApplet,
- java.beans.Beans,
- javax.swing.RepaintManager.
Современные браузеры давно не поддерживают апплеты, поэтому хранить этот API в платформе больше незачем.
Java 26 не пытается удивить одной эффектной фичей. Вместо этого релиз понемногу двигает платформу вперёд: где-то делает язык удобнее, где-то — рантайм быстрее, а где-то — правила строже и честнее. В этом смысле Java 26 хорошо показывает нынешний курс платформы: меньше магии, больше предсказуемости, безопасности и понятных инструментов для повседневной разработки.
Многие идеи из Java 26 почти наверняка всплывут уже в следующих LTS-версиях. А значит, разобраться в них заранее точно стоит.


