Why Doesn't a Missing Annotation Cause a Classnotfoundexception at Runtime

Why doesn't a missing annotation cause a ClassNotFoundException at runtime?

In the earlier public drafts for JSR-175 (annotations), it was discussed if the compiler and runtime should ignore unknown annotations, to provide a looser coupling between the usage and declaration of annotations. A specific example was the use of applications server specific annotations on an EJB to control the deployment configuration. If the same bean should be deployed on a different application server, it would have been convenient if the runtime simply ignored the unknown annotations instead of raising a NoClassDefFoundError.

Even if the wording is a little bit vague, I assume that the behaviour you are seeing is specified in JLS 13.5.7: "... removing annotations has no effect on the correct linkage of the binary representations of programs in the Java programming language." I interpret this as if annotations are removed (not available at runtime), the program should still link and run and that this implies that the unknown annotations are simply ignored when accessed through reflection.

The first release of Sun's JDK 5 did not implement this correctly, but it was fixed in 1.5.0_06. You can find the relevant bug 6322301 in the bug database, but it does not point to any specifications except claiming that "according to the JSR-175 spec lead, unknown annotations must be ignored by getAnnotations".

Why am I getting a NoClassDefFoundError in Java?

This is caused when there is a class file that your code depends on and it is present at compile time but not found at runtime. Look for differences in your build time and runtime classpaths.

Eclipse - java.lang.ClassNotFoundException

I've come across that situation several times and, after a lot of attempts, I found the solution.

Check your project build-path and enable specific output folders for each folder. Go one by one though each source-folder of your project and set the output folder that maven would use.

For example, your web project's src/main/java should have target/classes under the web project, test classes should have target/test-classes also under the web project and so.

Using this configuration will allow you to execute unit tests in eclipse.

Just one more advice, if your web project's tests require some configuration files that are under the resources, be sure to include that folder as a source folder and to make the proper build-path configuration.

What happens if import statements can not be resolved?

import statement is only important for the compiler. In bytecode all references to other classes are fully qualified. That's why superflous imports don't matter at runtime.

In your case JVM will try to load all classes that are required to load and verify A, so it will try to load B immediately, but dependant classes are loaded lazily only when they are needed. Check out the following example:

public class A {

public static void bar() {
new B().foo();
}

public static void main(String[] args) {
//bar();
}

}

Compile A.java and delete B.class. Without calling bar() method your program will run just fine. But once you uncomment piece of code actually using B class you'll get nasty:

Exception in thread "main" java.lang.NoClassDefFoundError: B
at A.bar(A.java:4)
at A.main(A.java:8)
Caused by: java.lang.ClassNotFoundException: B
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
... 2 more

If B is not available, you'll get NoClassDefFound or similar.

Compatibility of a Java runtime retention annotation in previous Java versions

I think then that I should [set] the source version to 1.8, and the target version to 1.6.

Actually, it is not possible to compile Java source files of newer source versions for older JVM target versions. Oracles and OpenJDKs javac will reject a compilation attempt where the -source version is higher than the -target version. (However, I couldn't find a specification denying it, even the manual doesn't mention that). The sole idea of javacs cross-compiling feature is that you can compile your old e.g. 1.6 Java files still for the old 1.6 JVM even when you are using a newer JDK for compilation.

The issue you are describing is the sort of reason for this. Since Java is using a sort of lazy dependency loading, the compiler can't guarantee that there will be an appropriated class at runtime for all the dependencies. This also applies to the standard library.

However, there are (unofficial) tools to compile the newer source idioms or byte code to older byte code versions. But that doesn't go for the standard library. If you wanna use newer classes, you have to provide them on your own. For this purpose, there exist some back ports for specific parts of the standard library.

Specifically about your annotation question:

I was not able to find any reliable specification to what should/might happen if the JVM encounters an annotated construct for which it could not retrieve the class file (I searched the Java virtual machine specification SE 8). However, I found a somewhat related reference in the Java language specification SE 8:

An annotation is a marker which associates information with a program construct, but has no effect at run time.

From JLS 9.7

This statement rather indicates that an annotation (present or not) should not have an influence on the execution of a JVM. Therefore, a exception (such as NoClassDefFoundError) because of a missing annotation were rather against this.

Finally, though the answers of this question, I found even more specific statements:

An annotation that is present in the binary form may or may not be available at run time via the reflection libraries of the Java SE platform.

From JLS 9.6.4.2

And

Adding or removing annotations has no effect on the correct linkage of the binary representations of programs in the Java programming language.

From JLS 13.5.7

This quite clearly states that missing annotations will not cause an error, but instead will be just ignored if examined by reflection.
And if you deliver a class annotated with a Java 1.8 standard library annotation, and it will be (somehow) executed on e.g. Java 1.6 JVM where that annotation is just not present, then this specifications denies that any error is generated.

This is also supported by the following test which I wrote: (notice the usage of reflection)

@TestAnno
public class Test {
public static void main(String[] args) {
Annotation[] annos = Test.class.getAnnotations();
for (Annotation a : annos) {
System.out.println(a);
}
}
}

@Retention(RetentionPolicy.RUNTIME)
@interface TestAnno {
}

If compiled, it yields a Test.class and a TestAnno.class. When executed the program outputs:

@TestAnno()

Because that is the one annotation applied to Test. Now, if the TestAnno.class is removed without any modifications to Test.class (which refers to TestAnno with LTestAnno; sequence in the byte code) and Test is executed again, it just does not output anything. So my JVM is indeed ignoring the missing annotation and does not generate any error or exception (Tested with a OpenJDK version 1.8.0_131 on Linux).

@Retention of Java type checker annotations

This is a good question.

For the purpose of static checking at compile time, CLASS retention would be sufficient. Note that SOURCE retention would not be sufficient, because of separate compilation: when type-checking a class, the compiler needs to read the annotations on libraries that it uses, and separately-compiled libraries are available to the compiler only as class files.

The annotation designers used RUNTIME retention to permit tools to perform run-time operations. This could include checking the annotations (like an assert statement), type-checking of dynamically-loaded code, checking of casts and instanceof operations, resolving reflection more precisely, and more. Not many such tools exist today, but the annotation designers wanted to accommodate them in the future.

You remarked that with @Retention(RetentionPolicy.CLASS), "the code does not have any runtime dependencies on the respective library." This is actually true with @Retention(RetentionPolicy.RUNTIME), too! See this Stack Overflow question:
Why doesn't a missing annotation cause a ClassNotFoundException at runtime? .

In summary, using CLASS retention costs a negligible amount of space at run time, enables more potential uses in the future, and does not introduce a run-time dependency.

In the case of the Checker Framework, it offers run-time tests such as isRegex(String). If your code uses such methods, your code will be dependent on the Checker Framework runtime library (which is smaller than the entire Checker Framework itself and has a more permissive license).



Related Topics



Leave a reply



Submit