3

Gradle officially quotes that the way to build a Java application with a JDK9+ to run on a Java 8 environment is to use the flag --release 8:

https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html

Which I try to do, however the compiler still throws an error:

module myapp.main {
^
  (use -source 9 or higher to enable modules)
1 error
:compileJava FAILED

According to that doc, and many stackoverflow articles (like this), the compiler should ignore the modules file, when targeting 8, but it doesn't. Why is that?

Gradle code:

compileJava {
    dependsOn(':compileKotlin')
    options.compilerArgs.addAll(['--release', '8'])
    doFirst {
        println classpath.asPath
        options.compilerArgs += [
                '--module-path', classpath.asPath,
        ]
        classpath = files()
    }
}
Wayneio
  • 3,466
  • 7
  • 42
  • 73
  • 1
    I think you misread. “`module-info.class` files will be ignored by the older JVMs” does not mean module-info.java will be ignored when compiling, it means a compiled module-info.class file created by a Java 9 compiler will be ignored at runtime. – VGR Apr 09 '18 at 14:54
  • @VGR Ah good point, I did misread that. However, removing the module-info.java actually causes the compiler to say every package and import does not exist - so it must need that file to exist. Especially since that post talks about the .class file being ignored, therefore the class file must have been compiled using the .java equivalent. – Wayneio Apr 09 '18 at 14:56

2 Answers2

2

The question is almost a year old, but maybe it'll still be helpful: VGR's answer is absolutely correct about why module-info.java is not ignored with --release 8 flag.

When it comes to how to compile main Java 8 classes + Java 9 module-info.java, this functionality is now supported by Gradle Modules Plugin since version 1.5.0. More details can be found here:

Tomasz Linkowski
  • 4,386
  • 23
  • 38
1

From the javac documentation:

--release release

Compiles against the public, supported and documented API for a specific VM version. Supported release targets are 6, 7, 8, and 9.

I read this as meaning that the compiler will treat any public methods and classes introduced in later versions like they don’t exist. For example, if code compiled with --release 8 imports or references java.lang.Module, the compiler will emit an error saying that that class doesn’t exist.

I do not see any part of that Gradle documentation stating that --release is the preferred or official way to target a Java 8 JVM. I only see examples that use --release as an example of how to add custom compiler options.

What it comes down to is, if you want compile for Java 8, you will need to exclude module-info.java.

If you want a single .jar that can both act as a module, and also be treated like a regular .jar by Java 8, you probably want a multi-release jar.

A multi-release jar is a concept introduced with Java 9. If you ignore all META-INF entries in the .jar other than the manifest, it looks exactly like a pre-9 .jar. No module-info, no Java 9+ class files.

All the Java 9 versions exist under META-INF/versions/9 in the .jar file. For instance, some entries in the .jar file might be:

  • META-INF/versions/9/module-info.class
  • META-INF/versions/9/com/example/myapp/MyApplication.class

And to signal to Java 9+ runtimes that the .jar is a multi-release .jar, you add this one manifest entry to the .jar:

Multi-Release: true

As for how to do all of this in Gradle, this blog recommends creating source sets. I don’t build with Gradle, so it’s not entirely clear to me from the documentation how to do it. Judging from the number of upvotes and the number of answers at How to make Multi-Release JAR Files with Gradle?, it appears other people aren’t clear on it either.

VGR
  • 40,506
  • 4
  • 48
  • 63