3

I want to use the @FunctionalInterface from Java 8 in my code, but I want to be able to use the generated class files with Java 6. I think then that I should the source version to 1.8, and the target version to 1.6.

I would be using @FunctionalInterface just for documentation, but I note that it has @Retention(RetentionPolicy.RUNTIME). If no one ever uses that annotation, will it cause problems?

If someone iterates over the annotations of my object at runtime, will it cause a missing class exception? But if that is true, how is it that how Google Guava can declare the JSR 305 annotation dependency to have a Maven <scope> of provided, which means annotations such as javax.annotation.Nonnull are missing at runtime, too, in Guava, without causing problems?

Let me ask it another way: if I use Google Guava in my project but don't include a JSR 305 dependency, do I really risk some error if I use reflection on the code? If so, what error will occur? If no error will occur, then analogously can I use the @FunctionalInterface annotation in source compiled with Java version 1.8 yet targeted to version 1.6 without any risk of runtime errors, even using reflection?

Garret Wilson
  • 14,167
  • 20
  • 115
  • 211
  • Did you read your link? It clearly states that you need to add JSR305 implementation so `javax.annotation.Nonnull` **isn't** missing at runtime. So yes, if at runtime the `FunctionalInterface` is missing and needs to be loaded, you'll get a `NoClassDefFoundError`. – Kayaman Aug 09 '17 at 17:28
  • Actually I _did_ read it a while ago, but apparently I didn't read it closely enough. It seems to say that if you don't actually use the JSR 305 annotations, you won't run into problems, but if you use reflection on Guava you _will_ run into problems if you haven't included the dependency in your own project. (Oddly, I've included the JSR 305 annotations in my own project with a `` of `provided` and never ran into problems... but I haven't tried reflection.) Maybe you could add a separate answer clarifying my use case so I could mark it as accepted. Thanks in any case. – Garret Wilson Aug 10 '17 at 22:32
  • OK, I read the original link again, and this is by no means straightforward. First of all, what you say about the link that "[i]t clearly states that you need to add JSR305 implementation so `javax.annotation.Nonnull` **isn't** missing at runtime" isn't quite true; the question and answer was about why someone suddenly didn't get JSR 305 support transitively anymore at _compile_ time. And after reading https://stackoverflow.com/a/3567969/421049 , it's unclear to me whether iterating over the JSR 305 annotations via reflection would cause problems if they were declared `provided`. I'm confused. – Garret Wilson Oct 07 '17 at 22:33
  • I suppose they wouldn't cause problems if you were to only iterate them, as per your link. However I'd imagine JSR305 annotations aren't usually just iterated over, you have specific hard dependencies that check whether distinct annotations are present and perform logic based on that. – Kayaman Oct 09 '17 at 12:54
  • @kayaman here you "suppose" and in your answer you "guess". I'm not trying to be mean, but your answer doesn't seem very definitive... I guess I asked the question to see if someone knew _for sure_, not just supposed or guessed. – Garret Wilson Oct 09 '17 at 15:35
  • I wasn't aware of the special handling for annotations regarding binary compatibility (I'm here to learn new things too you know). But in your position by this time I would've already tested what happens, which I assume you haven't done. Or at least you haven't mentioned it if you have. If you want, I can delete my answer as it doesn't provide an answer, however it does provide some related information so that's why I kept it for the time being. – Kayaman Oct 09 '17 at 16:31

2 Answers2

9

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).

Community
  • 1
  • 1
Cryptjar
  • 385
  • 3
  • 8
  • Cryptjar, this is simply a wonderful answer. You address all the aspects of the question, you back it up with references, and then you provide a test. Most importantly it got to the heart of the worry behind the question. This was fully worth the bounty I put up --- thank you! – Garret Wilson Oct 26 '17 at 22:02
  • See also https://stackoverflow.com/q/3567413/421049 and https://github.com/google/guava/issues/1096 . – Garret Wilson Oct 28 '17 at 15:17
0

As with any class loading situation, if the class isn't needed (or rather, doesn't need to be loaded), it doesn't matter if the class doesn't exist at runtime. Runtime annotations normally have the same problem, since if they're retained at runtime, it usually means that there's logic based on them, meaning their classes are loaded too.

But @FunctionalInterface doesn't have runtime logic, so... Why does @FunctionalInterface have a RUNTIME retention? Apparently not for any particularly compelling reason, just a side effect of it also being @Documented annotation.

So if you want to make sure there are no potential problems if someone (or some tool more likely (I don't mean a "tool", like a co-worker)) decides to enumerate the annotations in your classes, I guess you'd need to remove the annotations at pre-processing.

Kayaman
  • 67,952
  • 3
  • 69
  • 110
  • You "guess" I'd need to remove the annotations --- but you haven't told me why. And you haven't told me exactly what error would happen, and under what circumstances. The answer seems like little more than supposition, which is why I'm adding a bounty to the question to get something more definitive. – Garret Wilson Oct 09 '17 at 15:40