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.
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.
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With