БэкендМобильные приложенияJava / Kotlin

NullPointerException в Java: как найти null и исправить ошибку

Учимся читать NullPointerException в Java: находим строку, определяем null-ссылку, проверяем данные и исправляем причину без россыпи случайных проверок.

Кодик

Автор

5 мин чтения

NullPointerException возникает, когда Java пытается обратиться к полю, методу или элементу через ссылку, в которой находится null. Откройте первую строку stack trace, относящуюся к вашему коду, и разберите выражение слева направо. Цель не просто добавить if (x != null), а понять, почему обязательное значение не появилось или почему отсутствие значения не обработано.

Типичная строка выглядит безобидно: user.getName().trim(). Но в ней есть минимум две ссылки: user и результат getName(). Любая может быть null. Сообщение Java указывает файл и номер строки, однако не всегда автоматически объясняет бизнес-причину: пользователь не найден, поле не заполнено или метод нарушил обещание.

1Находим строку

Берём первый кадр stack trace из своего пакета, а не последнюю строку библиотеки.

2Называем ссылку

Делим длинное выражение на переменные и определяем, какая именно равна null.

3Чиним контракт

Инициализируем обязательное значение или явно обрабатываем допустимое отсутствие.

Что такое null и какие операции вызывают исключение

null означает, что ссылочная переменная не указывает на объект. Это не пустая строка, не число ноль и не пустой список. Исключение появляется в момент разыменования ссылки: вызова метода, чтения поля, определения длины массива, обращения к элементу массива или синхронизации. Простое сравнение ссылки с null допустимо и само по себе не падает.

ВыражениеРезультат
user.getName()NPE, если user равен null
name.trim()NPE, если name равен null
items.lengthNPE, если items равен null
user == nullБезопасная проверка
"admin".equals(role)Безопасно при null в role

Схема возникновения NullPointerException от null-ссылки до вызова метода
Исключение появляется не при хранении null, а при операции через отсутствующий объект.

Пустое и отсутствующее не одно и то же. Строка "" существует и имеет длину 0. Ссылка null не содержит объекта, поэтому вызвать у неё isEmpty() нельзя.

Читаем stack trace и уменьшаем проблемное выражение

Stack trace читают сверху вниз до первого упоминания своего класса. Верхняя строка сообщает тип исключения, следующие показывают цепочку вызовов. Найдя файл и номер, вынесите промежуточные результаты в отдельные переменные. Это временно делает код длиннее, зато отладчик и вывод сразу показывают, где появился null.

record User(String name) {}

public class Main {
    static String displayName(User user) {
        String rawName = user.name();   // user может быть null
        String cleanName = rawName.trim(); // rawName может быть null
        return cleanName.toUpperCase();
    }

    public static void main(String[] args) {
        User user = new User(null);
        System.out.println(displayName(user));
    }
}

// Пример указателя:
// Exception in thread "main" java.lang.NullPointerException
//   at Main.displayName(Main.java:6)
//   at Main.main(Main.java:12)
Что уже известно после чтения
  • Ошибка возникла внутри displayName, а вызов пришёл из main.
  • При показанных данных объект user существует, но его компонент name содержит null.
  • Исправление нужно выбирать на границе создания User или в правилах displayName, а не в println.

Пошаговое чтение stack trace Java и поиск первой строки своего кода
Начинайте с верхнего кадра своего класса: он сокращает область поиска до конкретного выражения.

Выбираем исправление по смыслу данных

Универсального оператора против null нет. Если значение обязательно, отвергните неверные данные как можно раньше. Если отсутствие допустимо, задайте явную ветку или запасное значение. Если метод поиска закономерно может ничего не найти, его контракт должен это выражать. Важен не синтаксис проверки, а понятное обещание вызывающему коду.

Смысл значенияПодход
ОбязательноПроверить через requireNonNull на входе
Отсутствие допустимоДобавить явную ветку if
Нужно значение по умолчаниюПодставить default осознанно
Поиск может ничего не вернутьВыразить результат как Optional
Сравнение с константойВызвать equals у константы
Проверка должна сохранять смысл. Если заказ обязан иметь владельца, молча заменить null на нового пустого пользователя опасно. Лучше остановить создание заказа с понятным сообщением.

Практика: исправляем отображаемое имя пользователя

Допустим, имя в профиле может быть пропущено, но сам объект пользователя обязателен. Тогда метод должен отдельно защитить эти два правила: быстро отклонить null вместо User и превратить пустое имя в подпись «Гость». После исправления проверьте нормальное имя, пробелы, null-поле и null-объект.

Четыре тестовых случая
  1. Добавьте Objects.requireNonNull(user, "user must not be null") первой строкой метода.
  2. Сохраните user.name() в переменную и проверьте её на null до вызова trim().
  3. После trim обработайте пустую строку как «Гость», иначе верните имя в верхнем регистре.
  4. Проверьте new User(" Ada "): ожидается ADA.
  5. Проверьте new User(null) и new User(" "): в обоих случаях ожидается ГОСТЬ.
  6. Передайте null вместо User: ожидается раннее исключение с вашим сообщением о нарушенном контракте.
Почему это исправление устойчиво
  • Обязательность объекта проверяется в одном понятном месте.
  • Допустимое отсутствие имени превращается в явно выбранный результат.
  • Ни один вызов метода не выполняется через ссылку, происхождение которой неизвестно.

Матрица исправлений null для обязательных и необязательных данных
Правильное решение зависит от контракта: запретить, обработать или выразить отсутствие.

Какие исправления маскируют NullPointerException

Быстрый патч иногда убирает текущий stack trace, но оставляет повреждённые данные. Особенно опасны проверки, которые просто прекращают работу метода, и широкие блоки catch. Через несколько шагов программа падает в другом месте, где связь с исходной причиной уже потеряна. Чините точку появления неверного состояния.

if добавлен вокруг каждой строки

Код перестаёт падать, но часть работы молча пропускается. Сначала определите, где null допустим по правилам программы.

Исключение ловят через catch Exception

Так теряются тип, строка и причина. NPE обычно указывает на дефект состояния, а не на событие для обычного управления потоком.

Длинную цепочку оставляют целиком

В a.getB().getC().trim() несколько кандидатов. Вынесите шаги и проверьте контракт каждого метода.

Optional используют как поле повсюду

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

Сообщение stack trace не читают

Без файла и номера строки разработчик исправляет похожий участок, а не реальное место падения.

Ищите место, где null появился впервые. Строка падения показывает использование. Причина часто находится раньше: запрос ничего не вернул, поле не заполнили или метод вернул значение вопреки договорённости. Технические детали сверены с официальным описанием NullPointerException в Java API.
Короткие ответы
null и пустая строка равны?

Нет. Пустая строка является объектом длины 0, а null не указывает на объект.

С какой строки stack trace начинать?

С первого кадра, который относится к вашему пакету и исходному файлу.

Может ли user существовать, а user.getName() вернуть null?

Да. В цепочке нужно проверять контракт каждого промежуточного результата.

Всегда ли нужна явная проверка значения на null?

Нет. Обязательные значения лучше валидировать на входе, а допустимое отсутствие обрабатывать по смыслу.

Почему catch Exception не является исправлением?

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

Тренируйте контракты на маленьких программах

Начните с типов, методов и объектов в курсе Java. Небольшие упражнения в Кодике позволяют увидеть stack trace рядом с кодом и менять по одной строке.

Когда освоите ссылки, переходите к ошибкам коллекций Java и разбору ConcurrentModificationException. Для первого серверного проекта пригодится REST API на Spring Boot.