lephix
lephix

Reputation: 1174

Weird NoClassDefFoundError of Java class

Environment: jdk1.7

javax.servlet-api-3.0.1.jar is needed for this test.

Reproduce steps:

  1. javac Test1.java -cp javax.servlet-api-3.0.1.jar build Test1.java with javax.servlet-api-3.0.1.jar

  2. javac Test2.java -cp javax.servlet-api-3.0.1.jar build Test2.java with javax.servlet-api-3.0.1.jar

  3. javac Test3.java build Test3.java

  4. java -classpath .:javax.servlet-api-3.0.1.jar Test3 run Test3 with dependency. Following is the output. It's OK here. hello world1 hello world2

  5. But when this command java Test3 run, Exception is thrown. The result at the end of this post. The weird thing is that "hello world1" could be printed out, but instead of printing out "hello world2" an exception is thrown.

Test1.java

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
public class Test1 {
    public void getRequest(HttpServletResponse resp) throws IOException {

        OutputStream os = resp.getOutputStream();
        resp.getOutputStream().close();
    }

    public void hello(String world) {
        System.out.println("hello " + world);
    }
}

Test2.java

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
public class Test2 {

    public void getRequest(HttpServletResponse resp) throws IOException {
        OutputStream os = resp.getOutputStream();
        try {
            resp.getOutputStream().close();
        } catch (Exception e) {
        }
    }

    public void hello(String world) {
        System.out.println("hello " + world);
    }
}

Test3.java

public class Test3 {
    public static void main(String[] args) {
        new Test1().hello("world1");
        new Test2().hello("world2");
    }
}

output of the final step. Test2.hello("world2") throw an exception:

hello world1
Exception in thread "main" java.lang.NoClassDefFoundError: javax/servlet/ServletOutputStream
    at cn.test.abc1.Test3.main(Test3.java:9)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.ClassNotFoundException: javax.servlet.ServletOutputStream
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 6 more

I am very confused with the Exception. Because I didn't use any code in the Class ServletOutputStream. And the difference of the Test1 and Test2 is only a try blocked.

This Question is not a duplicated question as it marked. Because JVM should not throw an Exception when a try block get involved.

Upvotes: 5

Views: 1638

Answers (2)

Beck Yang
Beck Yang

Reputation: 3024

After compare the output of javap -v -c Test?, I get the answer.
The javac create StackMapTable attribute for try/catch block of Test2.java.

If StackMapTable is found on class loading, JVM will perform bytecode verification and check referenced class. That's why it throw java.lang.NoClassDefFoundError.

https://stackoverflow.com/a/25110513 describe more detail about StackMapTable.

Old wrong answer

I compile the Test1.java and decompile it, the source code become:

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;

public class Test1 {
    public void getRequest(HttpServletResponse resp) throws IOException {
        ServletOutputStream os = resp.getOutputStream();
        resp.getOutputStream().close();
    }

    public void hello(String world) {
        System.out.println("hello " + world);
    }
}

Since the return object of resp.getOutputStream() is javax.servlet.ServletOutputStream. JVM still have to verify this class is sub-class of java.io.OutputStream before case it at runtime, so it try to load ServletOutputStream.class. But JVM cannot find it and throw ClassNotFoundException.

Upvotes: 2

lance-java
lance-java

Reputation: 27986

Makes perfect sense to me. Test3 references Test2 and Test1 and both need javax.servlet-api-3.0.1.jar on the classpath at runtime.

Upvotes: 0

Related Questions