11

Today I spent my afternoon with analysing a NoClassDefFoundError. After verifying the classpath again and again, it turned out that there was a static member of a class that threw an Exception that was ignored the first time. After that every use of the class throw a NoClassDefFoundError without a meaningful stacktrace:

Exception in thread "main" java.lang.NoClassDefFoundError: 
    Could not initialize class InitializationProblem$A
    at InitializationProblem.main(InitializationProblem.java:19)

That's all. No more lines.

Reduced to the point, this was the problem:

public class InitializationProblem {
    public static class A {
        static int foo = 1 / 0;
        static String getId() {
            return "42";
        }
    }

    public static void main( String[] args ) {
        try {
            new A();
        }
        catch( Error e ) {
            // ignore the initialization error
        }

        // here an Error is being thrown again,
        // without any hint what is going wrong.
        A.getId();
    }
}

To make it not so easy, all but the last call of A.getId() was hidden somewhere in the initialization code of a very big project.

Question:

Now that I've found this error after hours of trial and error, I'm wondering if there is a straight forward way to find this bug starting from the thrown exception. Any ideas on how to do this?


I hope this question will be a hint for anyone else analysing an inexplicable NoClassDefFoundError.

tangens
  • 39,095
  • 19
  • 120
  • 139
  • 3
    My first idea would be not to catch and ignore `Errors`. Ever. – Michael Myers Feb 05 '10 at 21:55
  • This was an initialization of some test classes collected from the filesystem. I tried to instantiate each class to see if it matches a given interface... Some classes are not able to be instantiated, e.g. abstract ones. So I catched all Errors and ignored them. – tangens Feb 05 '10 at 22:03
  • 1
    At the very least, print the stack trace. An empty catch block should be a red flag to you. (Have you ever used FindBugs? It will help catch a lot of things much earlier than you would on your own.) – Michael Myers Feb 05 '10 at 22:05
  • @tangens you don't need to instantiate a class to see if it implements an interface. To see if a `Class> someUnknownClass` implements some interface IInterface do `IInterface.class.isAssignableFrom(someUnknownClass)` – Geoff Reedy Feb 05 '10 at 22:34
  • @geoff-reedy: Actually, I did a Class.forName(), and even this causes the Error. Now I'm looking for a better way, see http://stackoverflow.com/questions/2210930/how-to-collect-all-classes-implementing-an-interface-at-runtime – tangens Feb 05 '10 at 22:39
  • @tangens, the answer is in your own comment. Don't ignore the exceptions, check if the class is abstract or not – Yoni Feb 05 '10 at 22:41

7 Answers7

17

Really, you shouldn't ever ever catch Error, but here's how you can find initializer problems wherever they might occur.

Here's an agent that will make all ExceptionInInitializerErrors print the stack trace when they are created:


import java.lang.instrument.*;
import javassist.*;
import java.io.*;
import java.security.*;

public class InitializerLoggingAgent implements ClassFileTransformer {
  public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new InitializerLoggingAgent(), true);
  }

  private final ClassPool pool = new ClassPool(true);

  public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)  {
    try {
      if (className.equals("java/lang/ExceptionInInitializerError")) {
        CtClass klass = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
        CtConstructor[] ctors = klass.getConstructors();
        for (int i = 0; i < ctors.length; i++) {
          ctors[i].insertAfter("this.printStackTrace();");
        }
        return klass.toBytecode();
      } else {
        return null;
      }
    } catch (Throwable t) {
      return null;
    }
  }
}

It uses javassist to modify the classes. Compile and put it in a jar file with javassist classes and the following MANIFEST.MF

Manifest-Version: 1.0
Premain-Class: InitializerLoggingAgent

Run your app with java -javaagent:agentjar.jar MainClass and every ExceptionInInitializerError will be printed even if it is caught.

Geoff Reedy
  • 34,891
  • 3
  • 56
  • 79
  • Thanks, Geoff. This is a better tutorial on how to write agents than anything that googled. – Pawel Veselov Jan 19 '13 at 01:07
  • Though this will still do it only the first time and will throw `NoClassDefFoundError` after that if I am not mistaken. Can we still get reference to the original `ExceptionInInitializerError` in subsequent calls? – Jus12 Sep 15 '14 at 21:22
  • 1
    Thanks, this was really helpful. One speedbump I hit while trying to do this with the Oracle JVM V1.7.0_80 is that you need to also add `Can-Retransform-Classes: true` to the manifest, otherwise you'll see something like `java.lang.UnsupportedOperationException: adding retransformable transformers is not supported in this environment`. Apparently that also applies to the IBM JVM, which is how I found the solution, via Google search. – Fanjita Jun 19 '15 at 12:49
14

My advice would be to avoid this problem by avoiding static initializers as much as you can. Because these initializers get executed during the classloading process, many frameworks don't handle them very well, and in fact older VMs don't handle them very well either.

Most (if not all) static initializers can be refactored into other forms, and in general it makes the problems easier to handle and diagnose. As you've discovered, static initializers are forbidden from throwing checked exceptions, so you've got to either log-and-ignore, or log-and-rethrow-as-unchecked, none of which make the job of diagnosis any easier.

Also, most classloaders make one-and-only-one attempt to load a given class, and if it fails the first time, and isn't handled properly, the problem gets effectively squashed, and you end up with generic Errors being thrown, with little or no context.

skaffman
  • 398,947
  • 96
  • 818
  • 769
4

If you ever see code with this pattern:

} catch(...) {
// no code
}

Find out who wrote it and BEAT THE CRAP OUT OF THEM. I'm serious. Try to get them fired--they do not understand the debugging portion of programming in any way, shape or form.

I guess if they are an apprentice programmer you might just beat the crap out of them and then let them have ONE second chance.

Even for temporary code--it's never worth the possibility that it will somehow be brought forward into production code.

This kind of code is caused by checked exceptions, an otherwise reasonable idea made into a huge language pitfall by the fact that at some point we'll all see code like that above.

It can take DAYS if not WEEKS to solve this problem. So you've got to understand that by coding that, you are potentially costing the company tens of thousands of dollars. (There's another good solution, fine them for all the salary spent because of that stupidity--I bet they never do THAT again).

If you do expect (catch) a given error and handle it, make sure that:

  1. You know that the error you handled is the ONLY POSSIBLE source of that exception.
  2. Any other exceptions/causes incidentally caught are either rethrown or logged.
  3. You aren't catching to broad an exception (Exception or Throwable)

If I sound aggressive and angry, it's because I've gotten screwed spending weeks finding hidden bugs like this and, as a consultant, haven't found anyone to take it out on. Sorry.

Bill K
  • 62,186
  • 18
  • 105
  • 157
  • This is good advice, but it doesn't answer the question that was asked. – Christopher Barber Mar 07 '12 at 19:42
  • I agree. usually when a question is well-answered and I see other advice I can give that it looks like people interested in that question will find of use, I like to throw it in, no harm and maybe it'll help someone out. This site shouldn't be all about following strict rules for no reason, it should be about helping others. If this kind of answer caused more damage for others than help I'd be against it, but the moderation system seems to take care of that. – Bill K Mar 07 '12 at 20:08
  • I have a similar problem. No catch block that ignores error and I do get `ExceptionInInitializerError`, the first time but after that I get a `NoClassDefFoundError`. Is there any way to link this to the original `ExceptionInInitializerError`? – Jus12 Sep 15 '14 at 21:23
  • @Jus12 That is a really bad case because your object truly is invalid (the NoClassDefFound is just reminding you it's broken). If you have the source code, catch it inside the static initialzier where it occurred, fix it/log it and do NOT rethrow. If you do not own the source code then make sure that all the conditions to build that object are met before you first REFERENCE it. This means you can't even instantiate a class that imports the problem class! You might even have to instantiate it reflectively, but you are really better off fixing it or getting rid of it. – Bill K Sep 15 '14 at 22:37
1

The only hints the error gives are the name of the class and that something went terribly wrong during initialization of that class. So either in one of those static initializers, field initialization or maybe in a called constructor.

The second error has been thrown because the class has not been initialized at the time A.getId() was called. The first initialization was aborted. Catching that error was a nice test for the engineering team ;-)

A promising approach to locate such an error is to initialize the class in a test environment and debug the initialization (single steps) code. Then one should be able to find the cause of the problem.

Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
1

Today I spent my afternoon with analysing a NoClassDefFoundError. After verifying the classpath again and again, it turned out that there was a static member of a class that threw an Exception that was ignored the first time.

There is your problem! Don't ever catch and ignore Error (or Throwable). NOT EVER.

And if you've inherited some dodgy code that might do this, use your favourite code search tool / IDE to seek and destroy the offending catch clauses.


Now that I've found this error after hours of trial and error, I'm wondering if there is a straight forward way to find this bug starting from the thrown exception.

No there isn't. There are complicated/heroic ways ... like using doing clever things with a java agent to hack the runtime system on the fly ... but not the sort of thing that a typical Java developer is likely to have in their "toolbox".

Which is why the advice above is so important.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
0

I really don't understand your reasoning. You ask about "find this bug starting from the thrown exception" and yet you catch that error and ignore it ...

Yoni
  • 10,171
  • 9
  • 55
  • 72
0

If you can reproduce the problem (even occasionally), and it's possible to run the app under debug, then you may be able to set a break point in your debugger for (all 3 constructors of) ExceptionInInitializerError, and see when they git hit.

vorburger
  • 3,439
  • 32
  • 38