JEP 533: в Java 27 Structured Concurrency меняет обработку исключений
Седьмой preview Structured Concurrency приносит три изменения: тип исключения, новый параметр в Joiner и упрощённый open-оверлоад. Разбираем, что переписывать.
Новости TprogerПосле семи preview Structured Concurrency в JDK 27 впервые избавляется от собственных preview-типов: FailedException заменён на стандартный ExecutionException. JEP 533 интегрирован в JDK 27 и приносит ещё два изменения: третий тип-параметр в Joiner и новый оверлоад open() — без явного Joiner при стандартной политике.
Structured Concurrency появилась в JDK 19 как инкубатор и прошла два инкубаторных раунда и несколько preview, постепенно сближая модель многопоточности с принципами структурного программирования: подзадачи живут строго внутри своей области видимости, отмена распространяется надёжно, а иерархии потоков отображаются в инструментах наблюдаемости.
Ключевые выводы
JEP 533 — что изменилось в седьмом preview
Три точечных улучшения в эргономике и типобезопасности
- FailedException заменён на ExecutionException: стандартные joiners теперь бросают
ExecutionExceptionвместо preview-специфичногоFailedException— тот же тип, что иFuture.get(). - Третий тип-параметр R_X:
Joiner<T, R, R_X>выносит тип исключенияjoin()в сигнатуру — контракт становится частью типа. - Новый open-оверлоад:
StructuredTaskScope.open(UnaryOperator<Configuration>)позволяет задать таймаут и имя без явной передачиJoiner. - Ранние access-сборки JDK 27 доступны с флагом
--enable-preview. - Форма API, заложенная в preview 5, сохраняется — дизайн сходится к финализации.
FailedException уходит — приходит ExecutionException
Главное изменение preview 7: joiners Joiner.allSuccessfulOrThrow(), anySuccessfulOrThrow() и awaitAllSuccessfulOrThrow() теперь бросают java.util.concurrent.ExecutionException вместо preview-специфичного FailedException. Причина исключения доступна через getCause() — так же, как в Future.get() со времён Java 5.
Смена типа сокращает разрыв между классическим и структурированным кодом. Привычный паттерн catch-switch переносится напрямую:
Команды, которые использовали FailedException в preview 5–6, должны заменить этот тип на ExecutionException при переходе на JDK 27.
Третий тип-параметр: исключение становится частью типа
StructuredTaskScope и интерфейс Joiner получают третий тип-параметр R_X, описывающий тип исключения, которое может бросить join(). Старая сигнатура Joiner<T, R> становится Joiner<T, R, R_X>.
Для прикладного кода, который использует стандартные joiners через open(), компилятор выводит все три параметра автоматически — исходный код выглядит так же, как раньше. Разница принципиальна для авторов кастомных joiners: предложение throws теперь становится частью типа, а не деталью реализации. Вызывающий код получает точный checked-exception контракт на join().
Новый open-оверлоад: конфигурация без лишнего Joiner
До preview 7 установить таймаут или имя области при политике по умолчанию (ждать всех, завершить со сбоем при первом падении подзадачи) требовало передачи явного Joiner рядом с оператором конфигурации. Теперь появился перегруженный StructuredTaskScope.open(UnaryOperator<Configuration>), который принимает только оператор конфигурации:
Оверлоад принимает UnaryOperator<Configuration> — то же более строгое типирование, которое было введено в preview 6. Политика join по умолчанию (ждать успеха всех или провала хотя бы одной подзадачи) сохраняется.
Что осталось без изменений
Структурные гарантии API остаются прежними: подзадачи наследуют привязки ScopedValue (JEP 506), JSON thread dump продолжает отображать иерархии областей для инструментов профилирования, StructureViolationException по-прежнему срабатывает при использовании области вне try-with-resources или форке из чужого потока.
Одно изменение JEP 533, выходящее за рамки трёх основных: метод onTimeout() интерфейса Joiner удалён и заменён на timeout(). Новый метод либо возвращает результат, либо при тайм-ауте бросает исключение с CancelledByTimeoutException как причиной. Авторам кастомных Joiner-реализаций потребуется переименовать метод и скорректировать логику обработки тайм-аута.
Preview 7 не является редизайном. Форма API, заложенная в preview 5, сохраняется. Изменения сосредоточены на эргономике и типизации, а не на структуре. Сужающийся охват каждого preview — разумный индикатор того, что дизайн сходится. JEP 533 не указывает сроков финализации, но Structured Concurrency прошла два инкубаторных раунда и шесть preview — это значительный путь.
Как попробовать
Предложение доступно в ранних access-сборках JDK 27 с флагом --enable-preview. Обратная связь принимается через mailing-листы OpenJDK и продолжает влиять на API.
- Скачать early-access сборку JDK 27 с jdk.java.net/27
- Добавить
--enable-previewпри компиляции и запуске - Заменить
FailedExceptionнаExecutionExceptionв catch-блоках - Обновить сигнатуры кастомных
Joiner-реализаций до трёх тип-параметров
Частые вопросы
Что такое Structured Concurrency в Java?
Structured Concurrency — API в java.util.concurrent, который рассматривает группу связанных подзадач как единицу работы. Подзадачи ограничены временем жизни родительского scope, отмена распространяется надёжно, а иерархии потоков видны в инструментах наблюдаемости. Доступна с JDK 19 (инкубатор), с JDK 21 — в preview.
Чем ExecutionException лучше FailedException?
ExecutionException — стандартный тип из java.util.concurrent, существующий с Java 5. Замена уменьшает количество preview-специфичных типов и позволяет повторно использовать существующий catch-код, написанный для Future.get().
Нужно ли переписывать весь код при переходе на JDK 27?
Только блоки, которые ловят FailedException: их нужно заменить на ExecutionException. Авторам кастомных joiners также потребуется обновить сигнатуры до трёх тип-параметров. Прикладной код, использующий стандартные joiners через open(), компилируется без изменений.
Когда Structured Concurrency выйдет из preview?
JEP 533 не устанавливает конкретных сроков. Однако API прошёл два инкубаторных раунда и семь preview-итераций — охват каждого следующего preview сужается, что указывает на сходимость дизайна. Финализация возможна в одном из следующих релизов JDK.
Как ScopedValue связан со Structured Concurrency?
ScopedValue (JEP 506) позволяет передавать неизменяемые данные по иерархии потоков без явной передачи параметров. Подзадачи, форкнутые внутри StructuredTaskScope, автоматически наследуют привязки ScopedValue от родительского потока.
Выводы
Preview 7 не меняет форму API — он делает её честной. Тип исключения теперь в сигнатуре, а не в документации.
Три изменения в JEP 533 — замена FailedException, третий тип-параметр и новый оверлоад open() — продолжают линию на сближение Structured Concurrency с идиомами стандартной библиотеки. Дизайн, по всем признакам, сходится: если вы следите за эволюцией Java-конкурентности, это хороший момент попробовать JDK 27 early-access и отправить обратную связь в OpenJDK.