Команда Spring АйО подготовила перевод статьи о том, как JSpecify наконец превращается из «ещё одного стандарта для библиотек» в рабочую основу null-безопасности для всей Java-экосистемы. IntelliJ IDEA 2025.3 впервые согласовала свои проверки с NullAway, а Spring и JetBrains синхронизировали suppressions и поведение анализаторов. Итог — единый, предсказуемый null-анализ без сотен ложных предупреждений.


Хорошие новости: JSpecify наконец здесь

Внедрение JSpecify ускоряется. Вам даже не нужно «устанавливать» его — поддержка проверки null теперь приходит автоматически вместе с зависимостями.

Если вы вдруг пропустили долгую историю появления JSpecify и хотите быстро вспомнить, что это такое и зачем нужно — в конце поста вы найдёте ссылки на отличные вводные доклады и материалы. Они дают необходимый контекст, показывают практические примеры и понятно объясняют цели спецификации.

Фреймворки и библиотеки вроде Spring Boot 4, Spring Framework 7 и множество связанных библиотек из семейства Spring, JUnit 6 и Guava 33.4+ уже поставляются с аннотациями JSpecify. И это только начало — впереди ещё много свежих релизов.

Это значит, что как только вы обновляетесь, IntelliJ IDEA начинает анализировать ваш код с учётом семантики JSpecify. IDE получает более умный data-flow, полноценную поддержку generics и точные проверки на null, часто выявляя скрытые проблемы, о которых вы даже не подозревали.

В предстоящей IntelliJ IDEA 2025.3 JSpecify становится предпочтительным источником информации о null. Если он обнаружен в classpath, IDE автоматически распознаёт и даже генерирует аннотации JSpecify через быстрые исправления (quick fixes) и рефакторинги.

Это большой шаг вперёд для null-безопасности в Java — но он не снял всех сложностей разработки.

Сложная часть: миграция вашего прикладного кода

Как только библиотеки аннотированы, следующим узким местом становится ваш собственный код.

В теории миграция должна быть простой: используйте OpenRewrite или замените @Nullable и @NonNull на их аналоги из JSpecify через Structural Search and Replace.

На практике всё оказывается куда менее гладко.

JSpecify вводит типизированную модель аннотаций, которой старые фреймворки не пользовались. Аннотации на использовании типов применяются не только к объявлениям, но и к аргументам типов и generics — и это меняет то, как nullability протекает через вашу программу.

Комментарий от эксперта сообщества, Михаила Поливахи

Речь о том, что nullability аннотации JSpecify, в отличие от уже устаревших nullability аннотаций Spring-а или большей части других nullability аннотаций, имеют TYPE_USE Таргет. 

Это означает, что аннотации JSpecify, помимо прочего, могут располагаться например на generic параметрах:

public void sort(List<@Nullable Integer> list) {
    // sorting...
}

С одной стороны это сила и очень гибкая гранулярность, но с другой стороны, это означает, что просто поставив @NullMarked вы аннотировали не просто типы/методы/конструкторы и т.д, а ещё и TYPE_USE. Это сильно усложняет миграцию с условного JSR 305 на JSpecify.

Если вы попробуете просто добавить @NullMarked на уровне пакета, то внезапно все неаннотированные ссылки в этом пакете становятся not-null, и IDEA начинает выбрасывать предупреждения при каждом возможном несоответствии.

В результате процесс миграции сводится к тому, что вы вынуждены переаннотировать буквально всё.

Альтернативой может быть ограничение области действия, но тогда вам придётся жить со «смешанной» семантикой: либо терпеть ворох предупреждений, либо выключить анализ полностью.

Статические анализаторы вроде NullAway позволяют ограничивать проверки отдельными пакетами, но это, по сути, полумера — и результаты всё равно будут отличаться от того, что вы видите в IDEA.

Что мы узнали, тестируя миграции

В JetBrains мы столкнулись с теми же проблемами, тестируя миграцию JSpecify на примерах Spring-приложений и в EAP-сборках наших IDE 2025.3.

Мы также получили подробную обратную связь от команды Spring и ранних адоптеров, которые пробовали миграцию до выхода последних обновлений.

Выяснилось две ключевые проблемы:

  1. IntelliJ IDEA продолжала показывать предупреждения, даже если проект без ошибок проходил сборку с NullAway.

    Иногда это означает, что IDEA действительно нашла проблему, которую NullAway не покрывает. Но в этом примере так не было:

    Предупреждения IntelliJ IDEA были технически корректны с точки зрения спецификации JSpecify, так как ResponseEntity аннотирован @NullMarked на уровне пакета. Это подтвердили тесты и авторы спецификации. Однако ценности для разработчика эта информация почти не несёт.

    То есть неожиданно улучшенная спецификация, учитывающая generics, создала ещё больше проблем, выбрасывая множество предупреждений сразу после обновления библиотеки.

  2. Когда разработчики вынуждены использовать suppressions в рамках анализа, специфичного для фреймворка, им приходится дублировать их для каждого статического анализатора, потому что единого стандарта suppression-констант нет.

Комментарий от эксперта сообщества, Михаила Поливахи

Мы знаем, что, как правило, в Java мы suppress-им warning-и через@SuppressWarnings, например: 

@SuppressWarnings({"NullAway.Init"}) для NullAway ленивой инициализации полей.
- @SuppressWarnings({"PMD.CyclomaticComplexity"}) для PMD, чтобы игнорировать цикломатическую сложность определённой функции.

Дело в том, что @SuppressWarnings намеренно принимает строку, и JSpecify как спецификация поддерживается как инструментами CI (NullAway), так и фреймворками (Spring), так и IDE (IntelliJ и её инспекции). И единый формат для suppression-а проблем JSpecify отсутствовал.

Из-за расхождения поведения IDE и CI разработчики не могли получить единое достоверное представление о nullability. А если добиться согласованности требовало серьёзных ручных усилий, JSpecify рисковал остаться стандартом исключительно для библиотек, но не прикладного кода.

Всего пару недель назад на этом пришлось бы поставить точку. Нам бы оставалось сказать, что «в реальности всё сложно» и «Нельзя просто взять и встроить nullability в кодовую базу», даже с пятнадцатым стандартом.

Но опыт показывает: чего-то стоящего можно добиться только когда вендоры и пользователи работают вместе.

Итог: единая логика в IntelliJ IDEA 2025.3

Начиная с IntelliJ IDEA 2025.3 JSpecify будет не только поддерживаться — developer experience с ним в IDE будет согласован с другими инструментами анализа, включая NullAway.

Это означает:

  • Более согласованную интерпретацию спецификации JSpecify.

  • Лучшую синхронизацию предупреждений между IDE и build-тулчейнами.

  • Более мягкую обработку смешанных аннотаций во время миграции.

Наша цель — чтобы, если сборка проходит NullAway без ошибок, в IDE вы видели то же самое. Один источник истины для nullability.

По первой проблеме чрезмерных предупреждений мы открыли задачу IDEA-381329 после обсуждения с сообществом и анализа миграций Spring-приложений. Решение подсказал Chris Povirk, который оставил такой комментарий:

И с небольшими настройками инспекций огромное количество предупреждений просто исчезает.

То же самое касается второй проблемы — отсутствия стандарта suppression-констант для фреймворк-специфичного анализа. Даже при согласованной семантике разные инструменты по-разному интерпретируют suppressions.

Спецификация JSpecify сознательно не стандартизирует их, оставляя это на усмотрение каждого инструмента.

  • IntelliJ IDEA использует стандартные IDs инспекций, например:
    @SuppressWarnings("NotNullFieldNotInitialized")

  • NullAway использует собственные константы вроде
    @SuppressWarnings("NullAway")
    и более узкие, например
    @SuppressWarnings("NullAway.Init") для проблем инициализации.

Документация Spring Framework 7.0 рекомендует использовать @SuppressWarnings("NullAway.Init"), когда фреймворк-управляемая инициализация вводит статический анализатор в заблуждение:

@Component
public class OrderService implements InitializingBean {
    private Repository repo;

    @Override
    public void afterPropertiesSet() {
        // initialization logic happens here
    }

    public void process(Order order) {
        repo.save(order);  // ⚠️ NullAway may report "field repo not initialized"
    }
}

Хотя Spring гарантирует инициализацию, NullAway не видит lifecycle-callbacks и может ругаться на поле.

Официальная рекомендация:

@SuppressWarnings("NullAway.Init")
private Repository repo;

IntelliJ IDEA, в свою очередь, знает о Spring: она понимает @Autowired@Inject и lifecycle-интерфейсы, поэтому в большинстве случаев такие шаблоны не вызывают предупреждений вовсе.

Чтобы остановить расхождение, команды IDEA и NullAway синхронизировали работу:

  • IntelliJ теперь понимает suppression-константы NullAway, такие как NullAway.Init.

  • NullAway в свежих сборках принимает suppression-ID в стиле IntelliJ:
    NotNullFieldNotInitialized.

Работа координируется в задаче IDEA-376483.

В итоге suppressions стали переносимыми, а проверки nullability — согласованными между IDE и CI.

Работа продолжается

Несмотря на выход JSpecify 1.0.0 и широкое внедрение, работа над спецификацией и инструментами не прекращается.

Сейчас обсуждаются:

  • единые идентификаторы suppression-констант, которые понимали бы все инструменты;

  • новые аннотации для более тонких null-контрактов (аналогично @Contract в Spring и JetBrains-аннотациях);

  • способы облегчить миграцию крупным кодовым базам.

Эволюция JSpecify остаётся открытой, независимой и основанной на реальном пользовательском опыте.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Рекомендованные доклады и материалы

Ниже — надёжные и понятные ресурсы, которые отлично вводят в тему JSpecify, объясняют мотивацию и показывают практику.

Доклады

JSpecify: Java Nullness Annotations and Kotlin — David Baker
Понятный и практичный вводный доклад от инженера, участвующего в создании инструментов null-безопасности в Kotlin. Рассматривает место JSpecify в экосистеме, то, как Kotlin интерпретирует аннотации, выгоды для смешанных Java/Kotlin-проектов и почему важна типизированная null-семантика.

Null Safety in Spring Applications With JSpecify and NullAway — Sébastien Deleuze
Прицельный обзор того, как Spring Framework 7 внедряет JSpecify, как Spring Boot 4 использует null-безопасность во всём стеке, и как NullAway помогает обеспечивать единые null-контракты. Обязательно к просмотру тем, кто мигрирует Spring-приложения или рассматривает переход на JSpecify.

Статьи и документация

  • JSpecify Official Documentation — авторитетное описание спецификации, семантики и поведения аннотаций.

  • Spring Framework 7 — документация по null-безопасности — показывает, как Spring использует JSpecify и как разработчикам следует адаптировать свой код.

  • NullAway — JSpecify Support Guide — объясняет взаимодействие статического анализа с JSpecify.

  • OpenRewrite JSpecify Migration Recipes — практичные советы для перехода от аннотаций JetBrains/Jakarta на JSpecify.

Комментарии (0)