java.lang.NoClassDefFoundError: Could not initialize class

Недавно я столкнулся с проблемой при отладке интеграции с одним из сторонних приложений, которое поставляется ‘as is’ и работает на IBM WebSphere. Оно использует внутренние JMS-очереди сервера для коммуникации с нашими приложениями, развернутыми там же.

При миграции на новую версию сервера я столкнулся с NoClassDefFoundError, возникающей в одном из классов стороннего приложения. Этот exception генерируется при попытке создания экземпляра класса при помощи new или при первом вызове метода класса:

Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found.

Были проверены все shared libraries сервера приложений, но ошибка продолжала обваливать приложение. Проблему отложили “на потом”.

Несколько дней спустя я просматривал доклад Александра Маторина с одной Java-конференции и наткнулся на интересный кейс:

package com.lckdn.puzzler;

public class Main {
    public static void main(String[] args) {
        try {
            Test.run();
        } catch (Throwable e) {
            System.out.println(e);
            System.out.println(e.getCause());
            Test.run();
        }
    }
}

class Test {
    static {
        if (true) throw new RuntimeException();
    }

    public static void run() {
        System.out.println("Hi!");
    }
}

// Что будет выведено на консоль?

//1. Hi!
//2. RuntimeException
//3. ExceptionInInitializer
//4. NoClassDefFoundError

Подумайте, прежде чем ответить.

Правильный ответ – 4.

Вывод на консоль

Класс Test содержит блок статической инициализации в котором выбрасывается RuntimeException. Компилятор не позволит сотворить такое исключение напрямую, поэтому мы обманываем его, пряча ошибку в if. Static метод run() намеренно вызывается в блоке try-catch, это позволяет поглотить первую ошибку – java.lang.ExceptionInInitializerError. Она возникает из-за принудительной генерации RuntimeException в static-блоке, что становится очевидным, если посмотреть в результат выполнения System.out.println(e.getCause()). Ошибка инициализации “оборачивает” нашу ошибку. Далее снова пытаемся обратиться к классу Test вызвав Test.run(), но ведь он не был инициализирован! Повторная попытка приводит к выбросу java.lang.NoClassDefFoundError.

Такое поведение при возникновении ошибки в ходе статической инициализации описано в спецификации Java SE.

Эта находка (а также частичная декомпиляция приложения:) позволила по-другому взглянуть на проблему и понять её причину. Но это уже совсем другая история 🙂