When using Java toolchains, do you need to still s...
# community-support
l
When using Java toolchains, do you need to still set
sourceCompatibility
and
targetCompatibility
? Or would gradle take it from the toolchain version?
v
You indeed should not set them. And on global-level it would even produce an error if you do iirc.
l
It's unfortunate that nowhere in the guide there's a word about it: https://docs.gradle.org/current/userguide/toolchains.html
v
Maybe because common sense might suggest it? If you already configure "use Java 8 to compile this code", why should you need to additionally configure source and target compatibility? On the other hand, on specific tasks it might maybe sometimes still make sense in rare edge cases.
🤷‍♂️
But feel free to open a PR that improves the docs, or an improvement request ticket asking for it being changed, then you'll see what the Gradle folks think about it.
👍 1
m
I like using
options.release
and latest JVM (no toolchain). See also https://jakewharton.com/gradle-toolchains-are-rarely-a-good-idea/
👆 1
👎 1
v
Most things in that blog post are imho non-issues. And they only focus on things that might be bad. Of course the JavaDoc tasks should be configured to use the most recent toolchain, but you can easily do that for example. It totally ignores the pros you have, like a separation of the JVM running Gradle and the JVM needed to compile the code. It is not only about running Gradle with Java 17 but compiling against Java 8. It is also about using Java 8 (which is the minimum supported version) to run Gradle, but compile the code with Java 21, and that you cannot achieve with
-release
. And if you want to stay on the cutting edge of Java versions, then usually using a newly released Java version is usable via toolchains a few versions before it is supported to run Gradle with that version. ... Like always, both approaches have their pros and cons. But that blog article is very contra, not looking at both sides of the medal.
👍 1
m
Why would I want a different JVM to run Gradle vs compiling my Java code?
l
For us has been handy because not every service is in the same version, so you have whatever on your local, and then gradle will use the appropriate version for the given gradle project
👍 1
m
Yea, enforcing the same JVM version everywhere I get. Although I would prefer to enforce this at the top-level (in the Gradle wrapper itself maybe, there is some work at the moment around this)
v
> Why would I want a different JVM to run Gradle vs compiling my Java code? Why not? Because both should have nothing to do with each other, they are semantically totally separate. But as practical use-case, as I just said, you for example want to compile your code with Java 22, but Gradle 8.8 is not released yet (of course it is now, but imagine it is not) The only way is using toolchains. Also, if it is only for personal projects, or projects in a controlled environment, it might be ok to control the Java version used to run Gradle. But if you for example have some public project, then with JVM toolchains it will just build as expected for everyone trying to run the build as long as he runs Gradle with a Java version that is compatible, but does not need to make sure to set his
JAVA_HOME
to a version that is needed to run that build. ...
m
Why not?
More disk space, more heap space to allocate, no sharing of the JVM caches, etc...
you for example want to compile your code with Java 22, but Gradle 8.8 is not released yet
Yup 👍 Fair use case. Although that’s good incentive to move the ecosystem forward. I haven’t been blocked by this yet (I compile for Java 8 mostly 😅 )
with JVM toolchains it will just build as expected for everyone trying to run the build as long as he runs Gradle with a Java version that is compatible
Agreed but as said above, I prefer to do this higher up in the Gradle wrapper itself (which is doable now 🎉 )
v
More disk space
Not really. When you need to build 10 projects with 10 different JVMs you still need those JVMs and thus the space.
more heap space to allocate
Where? Why?
no sharing of the JVM caches
Which caches? Why?
which is doable now
Not that good so far, for example no auto-provisioning, only already available version can be used. But promising, yes. But even with that, I very much like the Java version used to run Gradle separated from the Java version used to build and run my code. They are just totally different and have totally different requirements.
m
I guess I’m an optimist. I expect the requirements to be the same at some point and my 10 different projects to all run fine with Java 34
I also expect that a single JVM with a huge heap can control its GC much better than multiple JVMs
Where I meet you is that JVM-isolation provides some sort of safeguards against memory leaks
There is stuff like pre-aot classes for an example. A JVM can “pre-link” some classes. If you have to optimize this for 10 JVMs, that’s more work
v
I also expect that a single JVM with a huge heap can control its GC much better than multiple JVMs
You have multiple JVMs either way. Gradle starts various worker daemons / compiler daemons / test execution daemons / ... The question is just which JVM is used for them, the one also used to run Gradle or another one. But either way it will be a sperate process.
m
Agreed
Which could be debatable actually. Could I run my javac task in-process? Might be cool 🤷
v
I guess we just have to agree to disagree this time. We agree often enough on most other topics. 😄
😄 1
🤝 1
m
Ahah yes!
no auto-provisioning, only already available version can be used.
Definitely agree on this one. Would love to get auto-provisioning. And a wrapper that doesn’t require Java preinstalled. That’d be a huge boost for adoption
👍 1
p
Could I run my javac task in-process?
Are you sure this will work with different classpaths?
m
Ahaha different classpaths will break things for sure 🙃
But I don’t see anything that fundamentally prevent this. It’s all code
How you manage resources is the big question. You have the OS, the JVM, classloaders in the JVM, maybe virtualization/Docker, etc... Each layer adds isolation (safety) and costs a bit.
v
And also has separate max memory settings. No need to configure the daemon to use 10 GiB max memory just in case you do a full 300 modules dist build where everything is done in-process, when having a few 256 MiB workers could do the same. 🙂
m
If you can run 40x256MB workers in parallel, you’ll need 10GB though?
v
Sure, but not in one process that then might persist for running other tasks. The workers can more easily be shut down when no longer needed.
👍 1
m
Yep
I wish we could give the user more control in the tradeoff, which I don’t think is really possible right now. You can adjust max memory and number of workers but it’s very coarse. It’s a lot of trial and error (or just
-Xmx
inflation for many teams...)
#hardproblems
o
> Q: When using Java toolchains, do you need to still set sourceCompatibility and targetCompatibility? Or would gradle take it from the toolchain version? > A: You indeed should not set them. Hey there! I just want to clarify some points here. It would be useful to specify the compatibility level along with the JDK version if we aim to achieve the following: • Ensure that all developers are using the same JDK version. Having a consistent environment is cool. • Use benefits of the latest JDK versions while compiling libraries that are compatible with older Java versions (case mentioned in Jake’s article) Do you know if there any specific reasons why using the latest JVM as a toolchain along with an older Java version specified in
--release
compiler flag wouldn’t be a good idea? ____________ Answer: It is allowed to use toolchains and specify
options.release
for compilation tasks
1
2
t
I can't see why it would be a bad idea. It might even be needed if you use annotation processors or javac plugins that require a recent JVM to run but you compile targeting an older JVM version. That being said, I think I wouldn't configure a toolchain here (unless maybe the current JVM is too low)
v
The question was about
sourceCompatibility
and
targetCompatibility
, which corresponds to
-source
and
-target
. This you can surely do:
Copy code
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}
tasks.compileJava {
    options.release = 8
}
But you could as well just use java language version 8 for the toolchain, unless you hit some bug in the Java 8 compiler or for some other reason need the newer compiler. You can also do
Copy code
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}
tasks.compileJava {
    sourceCompatibility = "8"
}
But this would have the same problems you have when using
-source
and
-target
instead of
--release
which is always preferable. This also works, but again has the problem of not using
--release
but
-source
and
-target
.
Copy code
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
    sourceCompatibility = JavaVersion.VERSION_1_8
}
I actually thought the latter would cause some validation failure by Gradle. Must be some other combination that was illegal.
👍 1
o
Thank you for the detailed explanation! Now it’s clear to me
👌 1