bazelbuild / bazelbuild/bazel

Allow per-target Java runtime selection for java_binary/java_test

Open
#22,866 12 comments 2 reactions 0 assignees View on GitHub
P4 team-Rules-Java type: feature request
Dominant language
Java
Stars
25.8k
Forks
4.6k
Avg merge
2d 20h
Merged PRs (30d)
72

Description

### Description of the feature request:

`java_binary` and `java_test` currently use toolchain resolution to discover the Java runtime toolchain to use, based on the value of `--java_runtime_version=` (and `--tool_java_runtime_version=`) command-line options (aside from platform matching). However, toolchain resolution is rather cumbersome if we want to use different Java runtime toolchains for different _targets_.

Going further, we often want two distinct `java_binary` targets to share many of the same `java_library` dependencies. Since `java_runtime_version` is part of the configuration, naïvely setting different `java_runtime_version`s on the two `java_binary`s with a transition will often result in shared `java_library`s being compiled twice, in two different configurations' output directories.

It would be great if users are allowed to optionally specify which Java runtime to use through an attribute, tentatively named `java_runtime`, on `java_binary`/`java_test`. If this attribute is not specified, those rules can fall back to the [current toolchain resolution logic](https://github.com/bazelbuild/bazel/blob/8d188388cb3f9a5d4252b3f07832fff6c819422c/src/main/starlark/builtins_bzl/common/java/java_semantics.bzl#L25-L26).

**Note:** This feature request does not concern _compile-time_ Java versions.

### Which category does this issue belong to?

Configurability, Java Rules

### What underlying problem are you trying to solve with this feature?

We have a large heterogeneous JVM monorepo that support both backend services and data jobs. Some targets (e.g., backend services) would like to run on a newer Java runtime, while the Java runtime version for data jobs are often constrained by the open-source framework we use. (E.g., Apache Flink [does not yet](https://nightlies.apache.org/flink/flink-docs-master/docs/deployment/java_compatibility/) support JDK 21.)

To our knowledge, there are three existing ways to change runtime JDK on a per-target basis. But they all leave something to be desired.

1. Use an incoming edge transition that changes `java_runtime_version` for the different binaries.
1. Two downsides of this approach are: (a) `java_binary`, being a built-in rule, does not allow setting incoming-edge transitions; (b) if we `bazel build` two binaries with different runtime JDKs at the same time, libraries shared by binaries will get compiled twice.
2. To resolve (b), we could additionally use an outgoing edge transition on `deps` to revert the `java_runtime_version` back to the "default" version. But that brings more complexity, and doesn't solve all the problems. (E.g., ~~Bazel's output directory computation uses the top-level target's configuration as the "default" configuration. If this "default configuration" can change for different targets, then developers will see spurious rebuilds.~~ This turns out to be false – I stand corrected.)
2. Use the `launcher` attribute of the `java_binary` to call a JDK other than the one Bazel thinks it should use. This requires us to write some C++ code (since `launcher` must be a `cc_binary`), doesn't allow sharing action cache keys between different architectures, and overall feels like a hack.
3. Switch out the [`_stub_template`](https://github.com/bazelbuild/bazel/blob/8d188388cb3f9a5d4252b3f07832fff6c819422c/src/main/starlark/builtins_bzl/common/java/java_binary.bzl#L757) private attribute on `java_binary` to use a script that looks for another JDK to call into. This relies on Bazel implementation details, and is even more of a hack.

### Which operating system are you running Bazel on?

macOS

### What is the output of `bazel info release`?

7.1.2

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.

_No response_

### What's the output of `git remote get-url origin; git rev-parse HEAD` ?

_No response_

### Have you found anything relevant by searching the web?

There are two instances of prior art:

* `java_toolchain` has long supported a `java_runtime` attribute to define which runtime to use when _compiling_ Java code.
* rules_scala added a way to select runtime Java version on a per-target basis back in 2022: https://github.com/bazelbuild/rules_scala/pull/1373

Their implementation allows something like this:

```bazel
scala_junit_test(
name = "JunitRuntimePlatform_test_runner",
size = "small",
runtime_jdk = "@bazel_tools//tools/jdk:remote_jdk11", # <<
suffixes = ["Test"],
tests_from = [":JunitRuntimePlatform"],
runtime_deps = [":JunitRuntimePlatform"],
)
```

I'm proposing exactly this `runtime_jdk` attribute, for `java_binary` or `java_test`.

### Any other information, logs, or outputs that you want to share?

_No response_

Contributor guide

Open the contributing guide

Research direction

Start by reading the toolchain logic in src/main/starlark/builtins_bzl/common/java/java_semantics.bzl and the java_binary implementation in src/main/starlark/builtins_bzl/common/java/java_binary.bzl. Compare the existing java_toolchain java_runtime attribute and the referenced rules_scala approach. Done means java_binary and java_test can select a per-target runtime while retaining the current fallback behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
build-system
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.