NoClassDefFoundError vs ClassNotFoundException

java.lang.NoClassDefFoundError i java.lang.ClassNotFoundException brzmią bardzo podobnie ze względu na swoje nazwy i są często mylone. Tak naprawdę ich znaczenie jest różne.

Spójrzmy na hierarchię klas:
Exception Structure

  • obie klasy pochodzą z JDK 1.0,
  • są podklasami klasy Throwable,
  • NoClassDefFoundError jest unchecked – podtyp Error,
  • ClassNotFoundException jest checked – podtyp Exception,
  • oba „wyjątki” rzucane są w runtime’ie,
  • związane są z „niedostępnością” zależnej klasy i/lub niepoprawnym classpath.

NoClassDefFoundError

  • jest to błąd krytyczny,
  • najczęściej pojawia się podczas wywoływania new,
  • rzucany jest podczas próby załadowania (w runtime’ie) definicji klasy przez JVM lub classloader, a klasa (plik .class) nie jest fizycznie dostępna. Oznaczać to może, że:
    1. – klasa ta była dostępna podczas kompilacji, a nie jest dostępna podczas uruchamiania,
      – klasa jest dostępna, ale jest w niezgodnej wersji (Javy lub biblioteki),
      classpath został niepoprawnie ustawiony,
      – pojawił się błąd inicjalizacji, np. w bloku lub polu statycznym.
  • kolejnym przypadkiem może być sytuacja, gdy inny (parent) classloader załadował tą klasę i nie jest ona widoczna dla danego (child) classloadera (nie może być załadowana drugi raz); najczęściej taka sytuacja zdarza się w środowisku serwerów aplikacji, np. WebSphere.

ClassNotFoundException

  • jest to wyjątek checked; program nie jest przerywany, działa exception handling,
  • rzucany jest gdy aplikacja (w runtime’ie) próbuje znaleźć i załadować klasę na podstawie jej nazwy, ale jej definicja nie może być znaleziona i załadowana. Dzieje się tak na przykład podczas dynamicznego ładowania klas, tj. używania metod forName (z Class), loadClass, findClass lub findSystemClass (z ClassLoader). Istnienia wszystkich zależnych klas podawanych jako literał nie da się sprawdzić w czasie kompilacji,
  • kolejną sytuacją jest przekazywanie niedostępnej klasy w parametrze metody, która jako argument przyjmuje interfejs, który spełnia wspomniana klasa,
  • innym przypadkiem może być błędne użycie classpatha zdefiniowanego w manifeście pliku .jar, zamiast właściwego,
  • często spotykany podczas używania driverów JDBC.

Jest zdecydowanie trudniejszy w namierzeniu w środowisku z wieloma ClassLoaderami. Np. w projekcie spakowanym do EARa z modułem WAR w środku. Biblioteki umieszczone w katalogu lib EARa są widoczne dla klas z WARa, ale żadne klasy spakowane do JARa znajdujące się w katalogu WEB-INF/lib w WARze nie mogą być widoczne przez inne moduły (np. inne WARy, EJB-JARy itd). Podobna sytuacja opisana jest tutaj.

Wyjątek jest na tyle popularny, że powstała stronka www.classnotfound.com, gdzie można przeszukiwać biblioteki w celu odnalezienia szukanej klasy:)

Poniżej zamieściłem kod, który pokazuje różnice w działaniu poszczególnych wyjątków.

public class User {
}

public class UserService {

	public void createUser() {
		new User();
	}

	public void createUserClass() throws ClassNotFoundException {
		Class.forName("User");
	}
}

public class UserTester {

	public static void main(String[] args) throws Exception {
		UserService service = new UserService();
		service.createUser();
		service.createUserClass();
	}
}

Kiedy usuniemy plik User.class, zakomentujemy 19 linijkę, to po uruchomieniu programu otrzymamy ClassNotFoundException:

 java UserTester
Exception in thread "main" java.lang.ClassNotFoundException: User
	at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:169)
	at UserService.createUserClass(UserService.java:11)
	at UserTester.main(UserTester.java:8)

Natomiast gdy zakomentujemy 20 linijkę (a 19 odkomentujemy), to po uruchomieniu programu otrzymamy NoClassDefFoundError:

 java UserTester
Exception in thread "main" java.lang.NoClassDefFoundError: User
	at UserService.createUser(UserService.java:7)
	at UserTester.main(UserTester.java:9)
Caused by: java.lang.ClassNotFoundException: User
	at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
	... 2 more

Jeśli ktoś z Was ma inne doświadczenia z tymi wyjątkami, to zapraszam do komentowania.

5 komentarzy to “NoClassDefFoundError vs ClassNotFoundException”

  • Tomek says:

    W artykule podane są błędne numery linii.

  • dembol says:

    Podoba mi się artykuł jednak pojawiły się w nim pewne nieścisłości.

    1. Napisałeś „innym przypadkiem może być błędne użycie classpatha zdefiniowanego w manifeście pliku .jar, zamiast właściwego” jako przyczyny rzucania ClassNotFoundException z czym się nie zgadzam – w tym przypadku zostanie rzucony wyjątek NoClassDefFoundError natomiast jego przyczyną będzie oczywiście ClassNotFoundException. Warto to generalnie uściślić jeżeli próbujemy rozróżnić przyczyny powstawania obu wyjątków.

    Zrobiłem prosty test, który pokazuje taką sytuację:

    public class User {
    
        private EventBus bus = new EventBus();
    
        public void method() {
            bus.register(this);
            System.out.println("consumer registered");
        }
    
        public static void main(String[] args) {
            User u = new User();
            u.method();
        }
    }
    

    Kod został skompilowany z wersją guava-13.0.1. Manifest wygląda następująco:

    Manifest-Version: 1.0
    Archiver-Version: Plexus Archiver
    Created-By: Apache Maven
    Built-By: dembol
    Build-Jdk: 1.6.0_37
    Main-Class: pl.wp.dembol.examples.User
    Class-Path: guava-13.0.1.jar

    Po zmianie Class-Path na artefakt nieistniejący otrzymuję:

    java -jar examples-1.0-SNAPSHOT.jar
    Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/eventbus/EventBus
    	at pl.wp.dembol.examples.User.(User.java:11)
    	at pl.wp.dembol.examples.User.main(User.java:19)
    Caused by: java.lang.ClassNotFoundException: com.google.common.eventbus.EventBus
    	at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    	at java.security.AccessController.doPrivileged(Native Method)
    	at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    	... 2 more
    

    2. NoClassDefFoundError – „kolejnym przypadkiem może być sytuacja, gdy inny (parent) classloader załadował tą klasę i nie jest ona widoczna dla danego (child) classloadera (nie może być załadowana drugi raz); najczęściej taka sytuacja zdarza się w środowisku serwerów aplikacji, np. WebSphere.”.

    Czy mógłbyś przedstawić jakiś konkretny przykład do tej przyczyny?

    3. Do ClassNotFoundException – jako przyczynę warto podać mechanizm serializacji. W przypadku deserializacji obiektu (metoda readObject() klasy ObjectInputStream) może zostać także rzucony wyjątek ClassNotFoundException gdy klasa zserializowanego obiektu nie istnieje w przestrzeni adresowej.

  • Nowaker says:

    > ClassNotFoundException to wyjątek checked; program nie jest przerywany, działa exception handling

    Ponieważ cały artykuł jest porównaniem, rozumiem że autor chce jednocześnie przekazać, że NoClassDefFoundError przerywa program, bo nie działa exception handling, co nie jest prawdą. Każdy Throwable można złapać – niezależnie czy jest to Error czy Exception.

    Poza tym, w czasach, gdy mamy OSGi i inne cuda, Errory to już nie zawsze „błędy krytyczne”. NoClassDefFoundError to właśnie wyjątek od tej reguły – OSGi’owe aplikacje normalnie je obsługują i aplikacja działa dalej, tylko np. nie załadowała jakiegoś pluginu. Innym wyjątkiem od reguły jest IOError – teoretycznie powinien występować „when a serious I/O error has occurred”, ale implementacja java.io.Console#readLine robi catch IOException, throw IOError.

    NoClassDefFoundError jest po prostu najgorszym możliwym przykładem „błędu krytycznego”, a dobrym przykładem jest prawie każdy inny Error – tyle chcę powiedzieć. 😉

    • Barista says:

      Co do ClassNotFoundException, to chodzi o to, że język wymaga zastosowania exception handling.

      O OSGi nie pisałem, bo nie mam zbytniego doświadczenia. Twoje przykłady uzupełniają temat:)

Leave a Reply for Barista