Какую @NotNull в Java выбрать? Разбираем под капотом

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Проклятие NullPointerException: выбираем правильную @NotNull

Если вы хоть раз писали на Java, то наверняка сталкивались с NullPointerException (NPE). Это, пожалуй, самая частая ошибка, которая валит продакшн. Разработчики придумали элегантный способ борьбы с ней — аннотации @NotNull и @Nullable. Но вот незадача: в мире Java существует не менее десятка разных реализаций этой аннотации от разных вендоров. Какую выбрать? Почему их так много? И что происходит «под капотом», когда вы ставите @NotNull?

На Stack Overflow вопрос "Which @NotNull Java annotation should I use?" собрал тысячи просмотров и сотни голосов. Это настоящая головная боль для новичков и профи. В этой статье мы разложим по полочкам все основные варианты, покажем реальный код и дадим чёткий план действий. Поехали!

Зачем вообще нужны аннотации @NotNull?

Java — язык со статической типизацией, но он не умеет различать null и не-null ссылки на уровне типов (в отличие от Kotlin или Rust). Аннотации — это метаданные, которые говорят компилятору, IDE или статическому анализатору: «Эй, смотри, это поле никогда не должно быть null!».

Использование @NotNull даёт три ключевых преимущества:

  • Раннее обнаружение ошибок — IDE подсветит потенциальный NPE ещё до запуска.
  • Документация кода — разработчик сразу видит контракт метода.
  • Интеграция с инструментами — Lombok, Checker Framework, FindBugs и другие используют эти аннотации для кодогенерации и проверок.

Проблема в том, что каждая крупная экосистема (JetBrains, Google, Eclipse, Oracle) создала свою версию. Давайте разберём их по порядку.

Основные кандидаты: кто есть кто

1. javax.annotation.Nonnull (FindBugs / JSR-305)

Это был первый «стандарт де-факто». Аннотация из пакета javax.annotation (JSR-305).

import javax.annotation.Nonnull;

public class UserService {
    public String greet(@Nonnull String name) {
        return "Hello, " + name;
    }
}

Плюсы: исторически популярна, поддерживается старыми инструментами (FindBugs, IntelliJ IDEA с плагином).
Минусы: JSR-305 застрял в статусе «draft» и никогда не стал частью Java SE. Библиотека com.google.code.findbugs:jsr305 давно не обновляется. Не рекомендуется для новых проектов.

2. jakarta.annotation.Nonnull (Jakarta EE)

После перехода Java EE в Eclipse Foundation, пакет javax.* был переименован в jakarta.*. Аннотация jakarta.annotation.Nonnull — это современный наследник.

import jakarta.annotation.Nonnull;

public class RestController {
    public void process(@Nonnull String data) {
        // data гарантированно не null
    }
}

Плюсы: официально поддерживается, часть Jakarta EE 9+, есть шанс на стандартизацию.
Минусы: требует добавления зависимости jakarta.annotation-api. Не все инструменты анализа обновились.

3. org.jetbrains.annotations.NotNull (IntelliJ IDEA)

Самая популярная аннотация среди пользователей IntelliJ. Распространяется в библиотеке org.jetbrains:annotations.

import org.jetbrains.annotations.NotNull;

public class DataValidator {
    public boolean validate(@NotNull String input) {
        return input.length() > 3;
    }
}

Плюсы: отличная интеграция с IntelliJ IDEA (подсветка, инспекции, автогенерация проверок). Лёгкая зависимость (всего 26 КБ).
Минусы: привязка к IDE JetBrains. Если команда использует Eclipse или VS Code — эффект меньше.

4. lombok.NonNull (Project Lombok)

Lombok пошёл дальше: он не просто помечает параметр, но и генерирует код проверки на null.

import lombok.NonNull;

public class OrderService {
    public void createOrder(@NonNull String itemId, @NonNull Integer quantity) {
        // Lombok сам сгенерирует: if (itemId == null) throw new NullPointerException(...)
        System.out.println("Order for " + itemId + " x" + quantity);
    }
}

Плюсы: реальная защита runtime, не только статический анализ. Минимум кода.
Минусы: Lombok — это «магия» (обработка аннотаций на этапе компиляции). Не все любят скрытый код. Может конфликтовать с другими инструментами.

5. org.checkerframework.checker.nullness.qual.NonNull (Checker Framework)

Самый мощный, но и самый строгий инструмент. Это не просто аннотация, а целый фреймворк для статического анализа типов.

import org.checkerframework.checker.nullness.qual.NonNull;

public class PaymentProcessor {
    public void charge(@NonNull String cardNumber, int amount) {
        // Checker Framework докажет, что cardNumber не null на этапе компиляции
    }
}

Плюсы: глубокая проверка типов (вплоть до generic'ов и массивов). Может предотвратить NPE ещё до запуска.
Минусы: сложная настройка, медленная компиляция, требуется обучение команды.

Как это работает под капотом?

Давайте заглянем внутрь. Аннотации @NotNull бывают двух типов по механизму действия:

Тип 1: Аннотации-маркеры (Type-use annotation)

Большинство современных аннотаций (Jakarta, JetBrains, Checker Framework) помечены как @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER}). Это означает, что они могут стоять в любом месте, где используется тип:

List<@NotNull String> names; // Каждый элемент списка не null
@NotNull String[] array;      // Массив не null (но элементы могут быть null!)

Компилятор и IDE считывают эти аннотации через Java Reflection API (с задержкой, так как аннотации хранятся в байт-коде, если указана RetentionPolicy.RUNTIME).

Тип 2: Аннотации с кодогенерацией (Lombok)

Lombok работает на этапе компиляции. Когда вы пишете @NonNull перед параметром, Lombok генерирует в байт-коде следующий блок:

// Исходный код
public void save(@NonNull String data) { ... }

// Сгенерированный код (упрощённо)
public void save(String data) {
    if (data == null) {
        throw new NullPointerException                

Блоги

Рекомендаций по книгам