5

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.

yole
  • 92,896
  • 20
  • 260
  • 197
lephix
  • 1,144
  • 13
  • 23
  • 1
    Your step 4) includes the cp, while step 3) does not. – Murat Karagöz Aug 04 '16 at 12:36
  • @MuratK. But why new Test1().hello("world1"); in Test3.java not throw exception? – lephix Aug 04 '16 at 12:38
  • 1
    Because there is nothing to throw.. – Murat Karagöz Aug 04 '16 at 12:39
  • @MuratK. `new Test2().hello("world2");` has no reason to throw an Exception either. – lephix Aug 04 '16 at 12:42
  • 1
    Well, should maybe read your exception which states that your Test3 did not even execute. – Murat Karagöz Aug 04 '16 at 12:45
  • 1
    @MuratK. You can see the output of the Step5. First line is the output of this line: `Test1().hello("world1");` So Test3 did executed. – lephix Aug 04 '16 at 12:48
  • did you try in `IDE`? – bananas Aug 04 '16 at 12:49
  • @AsteriskNinja Yep! All the same. – lephix Aug 04 '16 at 13:05
  • in eclipse try this if it is a web application (`right click on project>build path>deployment descriptor/assembly(depends on your version)>add>java build path entries>servlet jar>finish.`) then try restarting – bananas Aug 05 '16 at 03:47
  • Thanks @AsteriskNinja, but I am curious about the reason of the exception. – lephix Aug 05 '16 at 04:40
  • Error is caused by the try { } catch (Exception e) { } in `Test2.java`. If you remove the `try-catch` it works fine. But I'm unable to provide any reason for the behaviour at this moment. – Viswanath Lekshmanan Aug 05 '16 at 05:15
  • Making `hello` in `Test2` static and calling it directly caused the same exception, so this issue must happens when the JVM is trying to initialize `Test2` – xiaofeng.li Aug 05 '16 at 05:26
  • So some how having a try catch affected the class loading. Has anyone tried a different JVM implementation? I will. – xiaofeng.li Aug 05 '16 at 08:34
  • @lephix This is caused when their is a `throws` declaration in the method and a `try-catch` block inside the method. If the code is that cause `Exception` is not within the `try-catch` It tries to load the class ... :( If the post is re-opened, I can answer in detail. – Viswanath Lekshmanan Aug 05 '16 at 09:13
  • 1
    You are try/catching `Exception` but an `Error` is being thrown (`NoClassDefFoundError`). If you want to catch this you will need to catch either `Throwable` or `Error` – lance-java Aug 08 '16 at 09:59

2 Answers2

2

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.

Community
  • 1
  • 1
Beck Yang
  • 3,004
  • 2
  • 21
  • 26
  • if that is the case then why does this not happen after swapping the lines "new Test2().hello("world2")" above "new Test1().hello("world1")" ? – sumit Aug 04 '16 at 12:57
0

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.

lance-java
  • 25,497
  • 4
  • 59
  • 101