0

I have a class in a Tomcat app which has a static initializer. An Error is being thrown inside the static initializer.

The problem is that the error message shown by Tomcat does not include the "cause", i.e. the message and stack trace from that error are not shown.

I can reproduce the problem like this:

public class BadInitClass
{
  static
  {
    if (System.currentTimeMillis() > 0)
      throw new Error("I need to see this message");
  }

  public void doSomething()
  {
    System.out.println("functionality here");
  }
}

Now, if I call this class from a unit test:

@Test
public void testStaticInit() throws Exception
{
  new BadInitClass().doSomething();
}

Then the test prints out the stack trace and message:

java.lang.Error: I need to see this message
at mypackage.BadInitClass.<clinit>(BadInitClass.java:8)
at mypackage.BadInitClassTest.testStaticInit(UOneUtilitiesTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)

However, if I add the same code to my Servlet, then Tomcat does not print the message, only the following:

exception

javax.servlet.ServletException: Servlet.init() for servlet MyServlet threw exception
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)
    org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606)
    org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    java.lang.Thread.run(Thread.java:662)

root cause

java.lang.NoClassDefFoundError: Could not initialize class mypackage.BadInitClass
    mypackage.[calling code here]
    datcon.webmail.utils.DispatchedHttpServlet.init(DispatchedHttpServlet.java:77)
    MyServlet.init(MyServlet.java:57)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)
    org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606)
    org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    java.lang.Thread.run(Thread.java:662)

note The full stack trace of the root cause is available in the Apache Tomcat/6.0.37 logs.

I am running Tomcat 6.0.37.

Is this a bug in Tomcat? Is there any workaround?

I know that static initialisers are not generally a good idea, but I have some legacy code which uses them, and debugging these issues is very painful without the underlying error messages and stack traces.

I notice that the unit test does not have a NoClassDefFoundError -- is the issue something to do with a custom ClassLoader used by tomcat?

Rich
  • 15,048
  • 2
  • 66
  • 119

3 Answers3

1

I notice that NoClassDefFoundError doesn't allow inner exceptions: http://docs.oracle.com/javase/7/docs/api/java/lang/NoClassDefFoundError.html

Maybe this isn't fixable without patching Tomcat's class loader to throw a different type of exception in this case?

Rich
  • 15,048
  • 2
  • 66
  • 119
0

Got it -- the problem is that the error from the static initializer is only thrown the first time. The second and subsequent times that the class is accessed, a NoClassDefFoundError is thrown with no inner exception details:

This test demonstrates the issue (same "BadInitClass" from above).

public void testStaticInit() throws Exception
{
  // First attempt: the underlying message is available:
  try {
    new BadInitClass().doSomething();
  } catch (Throwable t) {
    assertThat(t, instanceOf(Error.class));
    assertThat(
        getFullStackTraceWithCauses(t),
        containsString("I need to see this message"));
  }

  // Second attempt: a NoClassDefFoundError with no "cause":
  try {
    new BadInitClass().doSomething();
  } catch (Throwable t) {
    assertThat(t, instanceOf(NoClassDefFoundError.class));
    assertThat(
        getFullStackTraceWithCauses(t),
        not(containsString("I need to see this message")));
  }
}

private String getFullStackTraceWithCauses(Throwable t)
{
  StringWriter writer = new StringWriter();
  t.printStackTrace(new PrintWriter(writer));
  return writer.toString();
}

This has nothing to do with Tomcat.

Rich
  • 15,048
  • 2
  • 66
  • 119
-1

what imports do you have in the class BadInitClass? There is a class used in BadInitClass that tomcat can't find.

maiklahoz
  • 135
  • 8
  • No, the `NoClassDefFoundError` is not being thrown because a class cannot be located; it is thrown due to an error in the static initializer. Did I not make that clear in the question? – Rich Nov 15 '13 at 11:18
  • ... or by Tomcat 6.0.37 when a static initializer fails. Try it for yourself. – Rich Nov 15 '13 at 11:23
  • This error is because the class BadInitClass can't be initialize by the JVM. I say my answer because the error can be an import of BadInitClass, or because the static zone is wrong, but I think that it is correct. – maiklahoz Nov 15 '13 at 11:31
  • If you don't throw the new exception, there is the same error? Maybe the tomcat try to initialize the class and catchs the Error and becuase of this tomcat throws NoClassDefFoundError – maiklahoz Nov 15 '13 at 11:39