Какие улучшения были внесены в NullPointerException в Java 14+?

«Какие улучшения были внесены в NullPointerException в Java 14+?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Начиная с Java 14, NullPointerException содержит подробную информацию о том, какой именно вызов вызвал исключение.

Эволюция фичи:

Версия Java Статус Как включить
Java 14 Предварительная просмотр (JEP 358) -XX:+ShowCodeDetailsInExceptionMessages
Java 15 Вторая предварительная просмотр (JEP 384) -XX:+ShowCodeDetailsInExceptionMessages
Java 16 Стандартная фича (JEP 396) Включено по умолчанию
Java 17+ Стабильная часть языка Всегда включено

Пример до улучшений:

public class Example {
    public static void main(String[] args) {
        User user = getUser(); // возвращает null

        // Старый вывод: NullPointerException
        System.out.println(user.getProfile().getName().toUpperCase());
    }
}

Вывод: NullPointerException (без деталей)

Пример после улучшений:

public class Example {
    public static void main(String[] args) {
        User user = getUser(); // возвращает null

        // Новый вывод с детализацией
        System.out.println(user.getProfile().getName().toUpperCase());
    }
}

Вывод в Java 16+:

NullPointerException: Cannot invoke "User.getProfile()" because the return value of "getUser()" is null

Другие примеры детализации:

// Случай 1: Поле объекта null
String text = null;
System.out.println(text.length());
// Output: Cannot invoke "String.length()" because "text" is null

// Случай 2: Элемент массива null
String[] array = {"a", null, "c"};
System.out.println(array[1].length());
// Output: Cannot invoke "String.length()" because "array[1]" is null

// Случай 3: Локальная переменная null
public void process(User user) {
    if (user == null) {
        throw new IllegalArgumentException("User cannot be null");
    }
    // Детализация всё равно работает
    System.out.println(user.getName().toUpperCase());
}

Преимущества:

  1. Быстрая диагностика — сразу видно, какой вызов вызвал NPE
  2. Уменьшение времени отладки — не нужно анализировать stack trace построчно
  3. Улучшение читаемости логов — понятные сообщения даже для сложных цепочек вызовов

Ограничения:

  • Не работает для toString() неявных вызовов
  • Может не показывать детали в highly optimized коде (после JIT-компиляции)
  • Незначительный overhead при создании исключения