How to produce code in Java 11, but target Java 8 and above?

I am working on a small library and for obvious reasons I would like to produce code using all the Java 11 features (except modules I guess for now), but I would like the library to be compatible with Java 8 and above.

When I try this:

javac -source 11 -target 1.8 App.java

I get the following message:

warning: source release 11 requires target release 11

...and when I look at the byte code I see that the version of the class is 0x37 (Java 11):

$ xxd App.class
00000000: cafe babe 0000 0037 ...

And Java 8 cannot load it:

Exception in thread "main" java.lang.UnsupportedClassVersionError: App has been
    compiled by a more recent version of the Java Runtime (class file version 55.0),
    this version of the Java Runtime only recognizes class file versions up to 52.0
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

How do people provide such compatibility? I am open to all build tools.

For me it seems easy just to transform high-level language (Java) into low-level (bytecode). It appears to me that when the high-level language changes, the low-level should stay the same. That is why I thought it was possible.

UPDATE

Guys, I don't think that this answer duplicates Move to OpenJDK-11 but compile in Java 8, because there the OP asks how to keep producing the code with Java 8 features, but target Java 11 (which is just a famous backward compatibility). My question is the other way around: I want to produce the code in Java 11, but target Java 8. I came across that question when I was researching the topic before posing the question. I didn't find it applicable to my situation.

The other question Can Java 8 code be compiled to run on Java 7 JVM does look similar to my question, but it was asked in 2013 and the bytecode obviously changed between Java 7 and Java 8.

I didn't think the bytecode changed that much since Java 8 that is why I asked this question.


Solution 1:

While conversion of classes compiled for JDK 11 to JDK 8 would be theoretically possible with a sophisticated tool, it’s not trivial. There are significant changes on the binary level.

First, JDK 11 introduced nest types, which eliminates the need to generate synthetic accessor methods when accessing private members of inner/outer classes. Of course, such access would fail in older versions.

It also introduced dynamic constants, though I don’t know whether the Java language exploits that feature anywhere. This is mainly intended for future versions.

Then, since JDK 9, string concatenation gets compiled using invokedynamic referring to java.lang.invoke.StringConcatFactory which is not present in Java 8.

A feature that could work, is private methods in interfaces, introduced in Java 9 as a language feature, but already handled on the binary level in Java 8.

Java 8 would also be unable to process module definitions, but I suppose, they would be ignored.

Solution 2:

No, you cannot compile Java 11 source to Java 8 binaries.

In javac terms, the -source parameter can't be greater than the -target parameter.

So, if you want to produce Java 8 binaries, your sources should be written in java 8 (or earlier). If you don't use any Java 11 language features, your sources basically already are in Java 8 so this shouldn't be too big a problem.

Note that you can still use a JDK 11 to compile the Java 8 source into Java 8 binaries. The JDK version can be greater than the source and/or target versions.

Note: the javac documentation says nothing about the -source parameter having to be less than or equal to the -target parameter. There's lots of non-official documentation, however. For example, https://stackoverflow.com/a/9261298/691074

As far as I can tell there's also not a single counter-example of actually getting such a situation to work.