Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird NoClassDefFoundError of Java class

Tags:

java

jvm

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.

like image 682
lephix Avatar asked Oct 30 '22 22:10

lephix


1 Answers

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.

like image 120
Beck Yang Avatar answered Nov 10 '22 17:11

Beck Yang