If I do ```java { toolchain { language...
# community-support
s
If I do
Copy code
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}
in my project, and I have a JDK 17 installed (that can compile to Java 11 bytecode), will Gradle use it, or provision exactly JDK version 11?
c
it will provision Java 11
s
I feared that 😞 So if I want to avoid unnecessary provisioning, I guess it's still better to set
targetCompatibility
directly than using the toolchains mechanism?
c
not necessarily. toolchains guarantees compatibility with Java 11; targetCompatibility will emit compatible bytecode (assuming there are no issues in Java 17 with doing so), but is susceptible to various corner cases where library definitions from newer Java versions can leak in. Is there some concern with the provisioning? It’s very quick and cached locally.
s
Unnecessary provisioning just does not feel right, IMO it just should be avoided. Gradle puts so much effort into build performance that I really wonder why toolchains behave like this.
c
not understanding the issue. It provisions the correct toolchain and caches it - much like other areas (dependency cache, build cache, configuration cache), you have a one-time hit and then are good-to-go.
s
Yeah, a one-time hit that is unnecessary. Gradle also doesn't redownload a dependency if it's already present in local Maven (!) repository.
c
Gradle also doesn’t redownload a dependency if it’s already present in local Maven (!) repository.
It had to do the one-time hit to download it initially, same as with toolchains.
c
I've been setting toolchain to 21, and downgrading with release, I "assume" I've only got jdk 21...
Copy code
tasks.compileJava {
  options.release = 11
}
v
It had to do the one-time hit to download it initially, same as with toolchains.
Indeed it does not @Chris Lee, Gradle checks for a file with the right checksum in maven local even for remote dependencies and if no maven local is configured otherwise to save the download time if Maven happened to have dowloaded the artifact already in the past.
I really wonder why toolchains behave like this.
I would really wonder if they did not, because that is what you asked it to do. You ask "use a Java 11 toolchain", not "use a toolchain able to produce Java 11 bytecode". For that you are after https://github.com/gradle/gradle/issues/16256 with which you could do it.
Gradle puts so much effort into build performance
But it luckily puts more effort into build correctness, so that we do not end up like Maven users which should use
clean
in practically every invocation to get a result as correct as possible. build correctness > build performance imho.
I guess it's still better to set
targetCompatibility
directly than using the toolchains mechanism?
I would say no, never. Using
*Compatibility
is dangerous. If you for example have a Java 21 toolchain, and have compatibility set to 11, and use in your code
ByteBuffer.flip
, then your result will be code that is compatible with Java 11 as far as the bytcode version concerns, but will fail at runtime with "method not found" if you are using Java 11 or Java 12. If you really want to avoid toolchains (I would not), at least use
release
instead of
*Compatibility
which makes sure those API changes do not bite you. But I personally would not so much be concerned about the performance. On CI you can easily assure that the toolchain is already available and on non-CI, it is one download that is then reused by everyone needing the same toolchain. You can also work-around it a bit by not setting the toolchain at all if the toolchain running Gradle is sufficient like in the work-around in the issue linked above, but makes sure to then also use
release
.
s
then your result will be code that is compatible with Java 11 as far as the bytcode version concerns, but will fail at runtime with "method not found" if you are using Java 11 or Java 12.
Good point that used methods might be missing in an older runtime. I haven't though about that case.
v
Actually the method is not even missing, the same code will compile if you use a Java 11 runtime
But up to Java 12
flip
was inherited from
Buffer
and had
Buffer
as return type. In Java 13 it got overridden in
ByteBuffer
and there returns
ByteBuffer
. And that breaks your neck if you compile against the wrong API.
That's one of the edge-cases mentioned above
s
Yeah, I should have written "byte differently", but actually I guess another edge case could indeed also be a missing method.
a
Another example could be
javax.
packages that were removed in Java11. So one could set toolchains to Java 8 and if Gradle would happily use any Java >= 8, then the build could work on a machine with Java8, but not on one with Java11.
v
That I did not understand actually, mind elaborating @Anze Sodja? If Gradle would use Java 8 like requested, you would have the same, it will work on machine with Java 8, but no on one with Java 11. If Gradle would use Java 11 with
release
set to 8, you would get the exact same result. If Gradle would use Java 11 without
release
it would not build as then the Java 11 API would be compiled against which misses the method and would fail compilation.
a
> If Gradle would use Java 11 with
release
set to 8, you would get the exact same result. So if Gradle would work like that when Java8 is requested, then with Java11 I believe you would get compilation errors that
javax
package doesn't exist (unless you add a dependency manually) if you use some
javax
in your code. While with Java8 compilation would pass. And if you remove
javax
package suddenly the project would compile also with Java11.
c
Worth also mentioning that Gradle checks popular alternative download locations for the JDK. I found out a while back that it checks for ASDF and that that can expire your configuration cache as well
v
I believe you would get compilation errors that
javax
package doesn't exist
I don't think so. That's exactly what
release
is for, to compile against API of older versions. And that is then equal to compiling with Java 8 toolchain As I said, with Java 11 but without
release
you would get the compile error you described. But initially you said with a Java 11 toolchain you would get code that runs on Java 8 - 10 but not with 11+, while with Java 8 you would not have that problem and that is what I don't understand what you meant.
Ah wait, I read your message again, now I got what you mean. Not that the compilation result would not work, but that it would fail to compile if only Java 11 is present and used. Sorry, read it wrongly.
But then, I still think this is not true, as long as
release
is set to
8
which it at least currently is, if you use the toolchains feature.
You even cannot prevent it being set, which causes then other problems, as there are cases that are not compatible with the
release
flag.
s
Bonus question: Is there a toolchains API to get the exact version of the JDK (incl. minor and path level) that was provisioned by Gradle? Getting that version ast build time, I mean.
v
Copy code
javaToolchains.compilerFor(java.toolchain).map { it.metadata.jvmVersion }
javaToolchains.compilerFor(java.toolchain).map { it.metadata.javaRuntimeVersion }
❤️ 1
s
Thanks, I was too deeply looking into the direction of
launcherFor
.
v
You can also do the same with
launcherFor
, all the tools should have the metadata field
I just picked a random one