168

Java 8 introduces important new language features such as lambda expressions.

Are these changes in the language accompanied by such significant changes in the compiled bytecode that would prevent it from being run on a Java 7 virtual machine without using some retrotranslator?

Arto Bendiken
  • 2,567
  • 1
  • 24
  • 28
Nicola Ambrosetti
  • 2,567
  • 3
  • 22
  • 38
  • possible duplicate of [Are there any specific examples of backward incompatibilities between Java versions?](http://stackoverflow.com/questions/1654923/are-there-any-specific-examples-of-backward-incompatibilities-between-java-versi) – Ciro Santilli OurBigBook.com May 04 '15 at 10:35

5 Answers5

151

No, using 1.8 features in your source code requires you to target a 1.8 VM. I just tried the new Java 8 release and tried compiling with -target 1.7 -source 1.8, and the compiler refuses:

$ javac Test -source 1.8 -target 1.7
javac: source release 1.8 requires target release 1.8
JesperE
  • 63,317
  • 21
  • 138
  • 197
  • 4
    No, I don't think it will. Java has a small share of the desktop market, but keeps that little share in a pretty tight grip. But it does hamper the adoption of new versions and features. I will not be able to use Java 8 features in the code I write for quite some time, since I want to avoid people having to upgrade their local Java installation. – JesperE Aug 15 '14 at 10:05
  • Why? "Yes" would imply the Java 8 can be compiled to run on a Java 7 VM, which is incorrect according to the Java 8 compiler. – JesperE Oct 28 '14 at 17:22
  • 5
    Now I see: Your "No" answers the question's headline, not the question's body. – Abdull Oct 28 '14 at 17:37
  • But then why do we call JAVA Write Once Run Anywhere if we cannot run code written in java 8 on a java 7 machine? – mnagdev Jun 08 '22 at 10:57
61

Default methods require such changes to the bytecode and the JVM that they would have been impossible to do on Java 7. The bytecode verifier of Java 7 and below will reject interfaces with method bodies (except for the static initializer method). Trying to emulate default methods with static methods on the caller side would not produce the same results, because default methods can be overridden in subclasses. Retrolambda has limited support for backporting default methods, but it can never be fully backported because it truly requires new JVM features.

Lambdas could run on Java 7 as-is, if the necessary API classes just would exist there. The invokedynamic instruction exists on Java 7, but it would have been possible to implement lambdas so that it generates the lambda classes at compile time (early JDK 8 builds did it that way) in which case it would work on any Java version. (Oracle decided to use invokedynamic for lambdas for future proofing; maybe one day JVM will have first-class functions, so then invokedynamic can be changed to use them instead of generating a class for every lambda, thus improving performance.) What Retrolambda does is that it processes all those invokedynamic instructions and replaces them with anonymous classes; the same as what Java 8 does at runtime when a lamdba invokedynamic is called the first time.

Repeating Annotations is just syntactic sugar. They are bytecode compatible with previous versions. In Java 7 you would just need to implement yourself the helper methods (e.g. getAnnotationsByType) which hide the implementation detail of a container annotation which contains the repeated annotations.

AFAIK, Type Annotations only exist at compile time, so they should not require bytecode changes, so just changing the bytecode version number of the Java 8-compiled classes should be enough to make them work on Java 7.

Method parameter names exist in the bytecode with Java 7, so that's also compatible. You can get access to them by reading the bytecode of the method and looking at the local variable names in the method's debug information. For example the Spring Framework does exactly that to implement @PathVariable, so there is probably a library method which you could call. Because abstract interface methods don't have a method body, that debug information doesn't exist for interface methods in Java 7, and AFAIK neither on Java 8.

The other new features are mostly new APIs, improvements to HotSpot and tooling. Some of the new APIs are available as 3rd party libraries (e.g. ThreeTen-Backport and streamsupport).

Summa summarum, default methods require new JVM features but the other language features don't. If you want to use them, you'll need to compile the code in Java 8 and then transform the bytecode with Retrolambda to Java 5/6/7 format. At minimum the bytecode version needs to be changed, and javac disallows -source 1.8 -target 1.7 so a retrotranslator is required.

Esko Luontola
  • 73,184
  • 17
  • 117
  • 128
  • 3
    Actually type annotations can be runtime visible. http://stackoverflow.com/questions/22374612/is-it-possible-to-access-java-8-type-informations-at-runtime – Antimony Feb 18 '16 at 23:31
33

As far as I know none of these changes in JDK 8 required the addition of new bytecodes. Part of the lambda instrumentation is being done using invokeDynamic (which already exist in JDK 7). So, from the JVM instruction set standpoint, nothing should make the codebase incompatible. There are, though, a lot of API associated and compiler improvements that would could make the code from JDK 8 difficult to compile/run under previous JDKs (but I have not tried this).

Maybe the following reference material can help somehow to enrich the understanding of how the changes related to lambda are being instrumented.

These explain in detail how things are instrumented under hood. Perhaps you can find the answer to your questions there.

Maroun
  • 94,125
  • 30
  • 188
  • 241
Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
  • 7
    No new bytecodes, but new structures. The verifier will puke. – Jonathan S. Fisher Jun 12 '14 at 03:38
  • @exabrial, what do you mean with "structures"? – Abdull Oct 28 '14 at 17:39
  • 12
    A good example is Interfaces. They can now contain methods. The Java7 verifier is not equipped to handle this. All of the old bytecodes are used, but in a new way. – Jonathan S. Fisher Oct 28 '14 at 21:53
  • 1
    I wonder how can scala compiler with so many language features achieve a target jvm release of even jdk5. – Marinos An Apr 05 '16 at 12:44
  • 2
    @MarinosAn What do you mean exactly? MI with traits that contain concrete methods, e.g. `class C extends A with B`, is implemented with normal interfaces `A` and `B` and companion classes `A$class` and `B$class`. class `C` simply forwards the methods to the static companion classes. Self-types are not enforced at all, lambdas are transpiled on compile-time to abstract inner classes, so is a `new D with A with B` expression. Pattern-matching is a bunch of if-else structures. Non-local returns? try-catch mechanism from the lambda. Anything left? (Interestingly, my scalac says 1.6 is the default) – Adowrath Apr 08 '17 at 16:50
  • 1
    Of course, self-types etc. are encoded in special class attributes and annotations so that scalac can use and enforce the rules when using already compiled classes. – Adowrath Apr 08 '17 at 16:52
  • @Adowrath then how does Scala have traits with default implementation? This looks like the default method in Java 8 interfaces. – Franklin Yu Sep 06 '17 at 15:24
  • @FranklinYu If the target is 1.8 or above: default methods, yeah. 1.7 and below? If `trait A { def foo: Int = 12 }` and `class C extends A`, you roughly get: `interface A { int foo(); }`, `class A$class { public static int foo(A $this) { return 12; } }` and `class C implements A { public int foo() { return A$class.foo(this); } }`. So the default method implementation gets moved to an `X$class` class, and in each implementing class of the trait, the compiler generates a stub method that just forwards to the method in `X$class`, giving `this` as an argument (for if `X.foo` were to call `X.bar`). – Adowrath Sep 07 '17 at 11:22
  • So code in Java 7 is unable to call default implementation of Scala interface when running on JRE 7? If that is acceptable to Scala users, why can't we do the same with Java 8? – Franklin Yu Sep 07 '17 at 13:34
  • @FranklinYu Oh, sorry. Yep, it can't, JRE 7 can't even load the new Scala Traits with default methods. But so can it not load Java 8 interfaces with default methods. Scala 2.12 and onwards has an explicit dependency on Java 8 in not just this part, but also in SAM types (i.e. `java.util.Function` and `scala.Function1` are both handled the same, and `a => a + 1` can compile to both of them) compilation, using `java.lang.invoke.LambdaMetaFactory`. – Adowrath Oct 08 '17 at 17:36
  • @Adowrath So Scala doens't really "achieve a target jvm release of even jdk5", does it? – Franklin Yu Oct 09 '17 at 15:26
  • @FranklinYu 2.12 and above not anymore, no (unless it can handle `-target:jvm-1.7`, but I'd have to try out). 2.11 and below? Yeah. (Although I think it's Java 6 and upwards since quite some time, don't know exactly whether that's accurate) – Adowrath Oct 09 '17 at 16:32
11

If you are willing to use a "retrotranslator" try Esko Luontola's excellent Retrolambda: https://github.com/orfjackal/retrolambda

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
-6

You can do -source 1.7 -target 1.7 then it will compile. But it won't compile if you have java 8 specific features like lambdas

kalgecin
  • 93
  • 1
  • 9