3 часа назад
Вышла Java 27

Вышла общедоступная версия Java 27. В этот релиз попало приблизительно 2500 закрытых задач и 9 JEP’ов. Release Notes можно посмотреть здесь. Цельный список изменений api – здесь.
Java 27 не является LTS-релизом, и у него будут выходить обновления только полгода (до марта 2027 года).
Загрузить JDK 27 можно по этим ссылкам:
Oracle JDK (лицензия NFTC)
OpenJDK (лицензия GPLv2 with Classpath Exception)
Рассмотрим все JEP’ы, которые попали в Java 27.
Primitive Types in Patterns, instanceof, and switch (Fifth Preview) (JEP 532)
Примитивные типы в паттернах, instanceof и switch, которые были в preview в Java 23, Java 24, Java 25 и Java 26, остаются на пятое preview без изменений.
Смысл новой фичи – это сопровождение примитивных типов в паттернах и операторах instanceof / switch:
// --enable-preview --source 27 Object obj = 42; if (obj instanceof int i) { // matches System.out.println("int: " + i); } switch (obj) { case int i -> System.out.println("int: " + i); // matches case double d -> System.out.println("double: " + d); default -> System.out.println("other"); }
Проверять можно также и то, попадают ли значения в диапазон типа:
int i = 42; if (i instanceof byte b) { // matches System.out.println("byte: " + b); }
double d = 3.0; switch (d) { case int i -> System.out.println("int: " + i); // matches case float f -> System.out.println("float: " + f); default -> System.out.println("other"); }
В примерах выше 42 попадает в диапазон byte ([-128; 127]), а 3.0 без потери точности приводится к int. Итак, это позволит более безопасно приводить одни числовые типы к другим, не прибегая к ручным проверкам диапазонов.
Подобные проверки могут быть полезны и в паттернах записей:
record JsonNumber(double d) {} var json = new JsonNumber(3.0); if (json instanceof JsonNumber(int i)) { // matches // ... }
Если до Java 23-27 типы выражений-селекторов в switch могли быть только int, short, byte и char и для них поддерживались только константные ветки (case 3 и т.п.), то сейчас поддерживаются все примитивные типы и ветки могут быть паттернами:
float f = 1.0f; switch (f) { case 0f -> System.out.println("0"); case float x when x == 1f -> System.out.println("1"); // matches case float x -> System.out.println("other"); } boolean b = "hello".isEmpty(); switch (b) { case true -> System.out.println("empty"); case false -> System.out.println("non-empty"); // matches }
Lazy Constants (Third Preview) (JEP 531)
программный интерфейс для ленивых констант, которое было в preview в Java 25 и Java 26, остаётся в preview в третий раз. В этом релизе были сделаны следующие изменения:
Удалены методы
LazyConstant::isInitializedиLazyConstant::orElse.Добавлен статический способ
Set::ofLazy.
Напомним смысл новой фичи. Для начала вспомним, как в Java можно реализовать отложенную инициализацию классическими средствами:
class OrderController { private Logger logger = null; Logger getLogger() { if (logger == null) { logger = Logger.create(OrderController.class); } return logger; } void submitOrder(User user, List<Product> products) { getLogger().info("order started"); ... getLogger().info("order submitted"); } }
В примере выше объект logger инициализируется в момент первого обращения. У такого подхода есть несколько проблем:
Любой доступ к полю
loggerдолжен происходить через методgetLogger(). Это можно забыть сделать.Код не является потокобезопасным: объект
loggerможет инициализироваться некоторое количество раз.Компилятор не может применить оптимизацию constant folding, так как поле
loggerне является final.
Частично проблем выше можно избежать, прибегнув к другим более сложным идиомам, например, double-checked locking или class holder. Тем не менее с double-checked locking код становится невероятно громоздким и хрупким (в частности, можно забыть вставить ключевое слово volatile), а также отсутствует constant folding. С class holder код становится более-менее простым и надёжным (и есть constant folding), но у этой идиомы есть серьёзные ограничения: она применима только к статическим полям и для каждого поля приходится объявлять свой собственный класс. Также можно использовать ConcurrentHashMap, тем не менее и у неё есть недостатки: отсутствует constant folding и есть проблемы, если функция возвращает null.
Теперь посмотрим, как исходник будет выглядеть с новым интерфейсом LazyConstant:
// --enable-preview --source 27 class OrderController { private final LazyConstant<Logger> logger = LazyConstant.of(() -> Logger.create(OrderController.class)); void submitOrder(User user, List<Product> products) { logger.get().info("order started"); ... logger.get().info("order submitted"); } }
Теперь для получения логгера нужно применять объект LazyConstant, который инкапсулирует в себе ленивую логику вычисления логгера. При первом вызове метода get() содержимое вычисляется путём вызова переданного Supplier’а. Если же содержимое уже вычислено, то оно просто возвращается. LazyConstant гарантирует, что Supplier вызовется не более одного раза, тем самым обеспечивая потокобезопасность.
Под капотом LazyConstant внедрён таким образом, что использует внутреннюю для JDK аннотацию @Stable для хранения содержимого в поле, не являющегося final. Эта аннотация даёт сигнал виртуальной машине, что поле ввода не будет меняться более одного раза, а значит виртуальная машина после установки может считать его константным значением, что открывает функция для constant folding. Итак, LazyConstant позволяет добиваться одновременно гибкости инициализации и хорошей производительности.
программный интерфейс равным образом даёт возможность разрабатывать не только значения с единичным содержимым, но и ленивые списки и словари. Приведём пример ленивого списка:
// --enable-preview --source 27 class Application { private static final List<OrderController> ORDERS = List.ofLazy(POOL_SIZE, _ -> new OrderController()); public static OrderController orders() { long index = Thread.currentThread().threadId() % POOL_SIZE; return ORDERS.get((int)index); } }
В примере выше список ORDERS – это список, который для каждого индекса вычисляет значение в момент обращения и не более одного раза. Таким образом, LazyConstant – это ещё и качественный вариант для написания кэшей.
В Java 28 выйдет четвёртое preview ленивых констант.
Structured Concurrency (Seventh Preview) (JEP 533)
Structured Concurrency, которое было в режиме preview в Java 21, Java 22, Java 23, Java 24, Java 25 и Java 26, остаётся в режиме preview в седьмой раз. В этом релизе есть изменения программный интерфейс:
У интерфейсов
StructuredTaskScopeиJoinerпоявился ещё один параметрR_Xдля типа исключения, который методjoin()может выбросить.Появилась новая перегрузка статического метода
StructuredTaskScope::openс одним параметромUnaryOperator(до этого была только релиз метода с двумя параметрами:JoinerиUnaryOperator).Методы
Joiner::allSuccessfulOrThrow,Joiner::anySuccessfulOrThrowиJoiner::awaitAllSuccessfulOrThrowтеперь создаютJoiner’ы, которые выбрасываютExecutionExceptionв случае исключения. Также к ним добавились перегрузки, принимающиеFunction, которые позволяют указать другой тип исключения.Статический метод
Joiner::awaitAllбыл удалён.voidспособJoiner::onTimeoutбыл заменён на методJoiner:timeout, который позволяет указать результат в случае таймаута.
Напомним, что Structured Concurrency – это подход многопоточного программирования, который заимствует принципы из однопоточного структурного программирования. Главная идея такого подхода заключается в следующем: если проблема расщепляется на несколько конкурентных подзадач, то эти подзадачи воссоединяются в блоке кода главной задачи. Все подзадачи логически сгруппированы и организованы в иерархию. Каждая подзадача ограничена по времени жизни областью видимости блока кода главной задачи.
В центре нового программный интерфейс класс StructuredTaskScope, у которого есть два главных метода:
fork()– создаёт подзадачу и запускает её в новом виртуальном потоке,join()– ждёт, пока не завершатся все подзадачи или пока scope не будет закрыт.
Пример использования StructuredTaskScope, где показана задача, которая параллельно запускает две подзадачи и дожидается результата их выполнения:
// --enable-preview --source 27 try (var scope = StructuredTaskScope.open()) { Subtask<String> user = scope.fork(() -> findUser()); Subtask<Integer> order = scope.fork(() -> fetchOrder()); scope.join(); // Join subtasks, propagating exceptions // Both subtasks have succeeded, so compose their results return new Response(user.get(), order.get()); }
Может показаться, что в точности аналогичный код можно было бы написать с использованием классического ExecutorService и submit(), но у StructuredTaskScope есть некоторое количество принципиальных отличий, которые делают код безопаснее:
Время жизни всех потоков подзадач ограничено областью видимости блока
try-with-resources. Методclose()гарантированно не завершится, пока не завершатся все подзадачи.Если одна из операций
findUser()иfetchOrder()завершается ошибкой, то другая операция отменяется автоматически, если ещё не завершена (в случае использования дефолтногоJoiner’аawaitAllSuccessfulOrThrow(), но возможны другие с другим поведением).Если главный поток прерывается в ходе ожидания
join(), то обе операцииfindUser()иfetchOrder()отменяются при выходе из блока.В дампе потоков будет видна иерархия: потоки, выполняющие
findUser()иfetchOrder(), будут отображаться как дочерние для главного потока.
Structured Concurrency должно облегчить написание безопасных многопоточных программ благодаря знакомому структурному подходу.
В Java 28 Structured Concurrency станет постоянным api.
Compact Object Headers by Default (JEP 534)
Компактные заголовки объектов, которые появились в Java 25 (в Java 24 – в экспериментальном режиме), теперь включены по умолчанию. Таким образом, опция -XX:+UseCompactObjectHeaders больше не нужна. Также компактные заголовки можно отключить, если запустить Java с противоположным ключом:
$ java -XX:-UseCompactObjectHeaders ...
Компактные заголовки являются результатом работы в проекте Lilliput. Теперь размер заголовков объектов в JVM уменьшился с 96/128 бит до 64 бит на 64-битных платформах. Компактные заголовки не только уменьшают размер кучи, но и могут усовершенствовать производительность благодаря более высокой скорости выделения новых объектов, более низкой нагрузки на GC и лучшей локальности данных.
Сжатие заголовков достигается за счёт объединения mark-слова (64 бит) и class-слова (64 или 32 бит, если включены сжатые указатели на классы) в одно 64-битное слово. В новой схеме указатели на классы всегда являются сжатыми, и количество бит для них уменьшается с 32 до 22. Identity хеш-код остаётся неизменным: 31 бит. Количество тег-битов становится на один больше (для GC self forwarding). Битов для возраста GC остаётся 4, как и было. Также 4 бита резервируются для Valhalla.
Работа в проекте Lilliput не закончена. В дальнейшем возможно ещё большое сжатие заголовков до 32 бит, что сократит потребление памяти ещё больше.
Make G1 the Default Garbage Collector in All Environments (JEP 523)
Сборщик мусора G1 стал включен по умолчанию во всех окружениях. Ранее в окружениях с одним процессором или небольшим количеством оперативной памяти (< 1792 MB) автоматически включался Serial GC. Так было потому, что в таких условиях Serial GC имел преимущества в пропускной способности и количеству потребляемой памяти. Однако за последние годы G1 достиг хорошего уровня производительности, поэтому такое переключение уже фактически не имеет смысла. Если в вашем конкретном случае сборщик мусора Serial GC всё ещё имеет лучшую производительность, то его можно активировать с помощью ключа -XX:+UseSerialGC.
JFR In-Process Data Redaction (JEP 536)
В JDK Flight Recorder появилась функция редактирования чувствительных данных, чтобы они не попадали в итоговые файлы записи в открытом виде. К таким данным относятся пароли, токены, секретные ключи и прочие подобные конфиденциальные информация. Редактировать можно три вида атрибутов: аргументы командной строки, переменные окружения и системные свойства. Для возможности указания конкретных атрибутов для редактирования, появились две новые подопции у опции -XX:FlightRecorderOptions: redact-argument (для аргументов командной строки) и redact-key (для переменных окружения и системных свойств). В частности, вот так может выглядеть запускание JFR, если нужно отредактировать переменные и свойства с именем confidential или CONFIDENTIAL, а также URL, содержащие имя пользователя и пароль:
$ export CONFIDENTIAL=SOME_SECRET $ java -XX:FlightRecorderOptions:'redact-key=confidential,redact-argument=https://*:*@*' \ -XX:StartFlightRecording:filename=dump.jfr \ -Dconfidential=ANOTHER_SECRET \ -jar application.jar https://john:YET_ANOTHER_SECRET@example.com/login --verbose
После запуска в файле JFR чувствительные данные будут заменяться на [REDACTED], в частности, событие jdk.JVMInformation будет выглядеть так:
jdk.JVMInformation { startTime = 17:39:02.196 (2026-02-15) jvmVersion = "Java HotSpot(TM) 64-Bit Server VM" jvmArguments = "-Dconfidential=[REDACTED] -XX:FlightRecorderOptions:redact-key=confidential,redact-argument=[REDACTED] -XX:StartFlightRecording:filename=dump.jfr" jvmFlags = "N/A" javaArguments = "-jar application.jar [REDACTED] --verbose" jvmStartTime = 17:39:02.050 (2026-02-15) pid = 43671 }
Если явно не указать redact-argument или redact-argument, то используются фильтры по умолчанию. В их список входят распространённые паттерны вроде *passwd*, *password*, *credential*, *secret*, *token* и другие.
PEM Encodings of Cryptographic Objects (Third Preview) (JEP 538)
программный интерфейс для кодирования криптографических объектов в формат PEM и декодирования обратно, которое было в preview в Java 25 и Java 26, остаётся на третье preview. В этом релизе есть несколько изменений api
PEMтеперь является обычным классом, а не записью, и у него появились конструкторы, принимающие Base64 в виде массивов байт.Оболочку
DEREncodableпереименован вBinaryEncodable.Методы
EncryptedPrivateKeyInfo::getKeyиEncryptedPrivateKeyInfo::getKeyPair, которые принималиProviderвторым аргументом, теперь принимают только один аргументKey.Способ
PEMDecoder::withFactoryпереименован вPEMDecoder::withFactoriesOf.Появился свежий тип исключения
CryptoException.
Напомним, что новое программный оболочку для кодирования в структура PEM позволяет кодировать самые разные криптографические сущности: открытые ключи, закрытые ключи, сертификаты и т.д.
Вот пример открытого ключа, закодированного в формате PEM:
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/kRGOL7wCPTN4KJ2ppeSt5UYB6u cPjjuKDtFTXbguOIFDdZ65O/8HTUqS/sVzRF+dg7H3/tkQ/36KdtuADbwQ== -----END PUBLIC KEY-----
Такой ключ можно декодировать с помощью нового класса java.security.PEMDecoder:
// --enable-preview --source 27 PEMDecoder decoder = PEMDecoder.of(); PublicKey key = (PublicKey) decoder.decode(data); IO.println(key);
Кодирование происходит с помощью класса java.security.PEMEncoder:
// --enable-preview --source 27 PEMEncoder encoder = PEMEncoder.of(); String data = encoder.encodeToString(key); IO.println(data);
Список всех криптографических объектов, которые можно кодировать/декодировать, лимитирован наследниками нового sealed интерфейса java.security.BinaryEncodable:
public sealed interface BinaryEncodable permits AsymmetricKey, KeyPair, PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo, X509Certificate, X509CRL, PEM, InternalBinaryEncodable { }
Среди наследников выделяется особенный класс java.security.PEM. Этот класс может содержать в себе любые PEM-данные. Он может пригодиться, когда для криптографического объекта в Java нет соответствующего api (например, запрос сертификата PKCS #10):
public final class PEM implements BinaryEncodable { String type(); // Cryptographic object type, from the header text // (e.g., "PRIVATE KEY") byte[] content(); // Base64-encoded PEM content byte[] leadingData(); // Any content preceding the PEM header byte[] decode(); // Decode Base64 content ... }
PEM pr = PEMDecoder.of().decode(pem, PEM.class);
В Java 28 PEM Encodings станут постоянным программный интерфейс.
Post-Quantum Hybrid Key Exchange for TLS 1.3 (JEP 527)
В реализации TLS 1.3 в JDK появилась сопровождение пост-квантовых гибридных схем обмена ключей. Эти схемы комбинируют ML-KEM с традиционным ECDHE (Ephemeral Elliptic-Curve Diffie-Hellman). Всего появилось 3 гибридных схемы:
X25519MLKEM768 (комбинация ECDHE c X25519 и ML-KEM-768)
SecP256r1MLKEM768 (комбинация ECDHE c кривой secp256r1 и ML-KEM-768)
SecP384r1MLKEM1024 (комбинация ECDHE c кривой secp384r1 и ML-KEM-1024)
Для включения новых схем от разработчика ничего не требуется: во время рукопожатия X25519MLKEM768 будет иметь наивысший приоритет и будет использоваться TLS-клиентом, если хост его поддерживает.
Выход новых пост-квантовых гибридных схем является очередным шагом в поддержке в JDK алгоритмов, устойчивых к потенциальным будущим атакам с помощью квантовых компьютеров. Ранее в Java 24 появилась поддержка ML-KEM и ML-DSA.
Vector api (Twelfth Incubator) (JEP 537)
Векторное api в модуле jdk.incubator.vector, которое появилось ещё аж в Java 16, остаётся в инкубационном статусе в двенадцатый раз.
Векторное программный оболочку остаётся так долго в инкубаторе, потому что зависит от некоторых фич проекта Valhalla (в основном, от value-классов), который появится только в Java 28. Поэтому вероятно в Java 28 векторное api перейдёт из инкубатора в статус preview.
Читают сейчас

49 минут назад
Игра «Миссия Луна»: построить лунную базу и решить, что создавать, когда всё идёт не по плану
В России начали разрабатывать компьютерную игру о жизни на Луне. «Миссия Луна» — многопользовательская кооперативная игра, где экипаж строит и обслуживает постоянную лунную колонию. Анонсируют проект

1 час назад
Perscale news #11. Fine-grained token, SQID, stdout log streaming
Всех категорически приветствую, любители нагрузки. Сегодня в выпуске: - Stdout log streaming - Fine-grained токены - SQID для публичных ID Читать далее

1 час назад
Bose представила открытые гарнитура Ultra Open Earbuds 2 и Sport Open Earbuds
Bose представила две модели открытых TWS‑наушников: второе поколение Ultra Open Earbuds и спортивные Sport Open Earbuds. Устройства крепятся с помощью клипс и используют технологию OpenAudio, с которо

2 часа назад
Исследователи из ETH Zurich научили роботизированную кисть ходить на пальцах и нажимать клавиши
Исследователи из лаборатории Soft Robotics Lab при Швейцарской высшей технической школы Цюриха научили антропоморфную роботизированную кисть передвигаться на пальцах. Робот умеет ползать по разным пов

2 часа назад
ИИ в тестировании: разбираем падения автотестов, оцениваем LLM и строим обвязку агентов
В августе прошёл Bugs Busters — митап для QA-специалистов от ЮMoney. Спикеры компании рассказали, как применяют ИИ для анализа причин падения e2e-автотестов, как устроен средство для подбора оптимальн