Skip to content

Fix forge dockerBuildNative builder stage to use Java 21 GraalVM image#15580

Merged
jamesfredley merged 2 commits into8.0.xfrom
fix/forge-graalvm-builder-java21
Apr 16, 2026
Merged

Fix forge dockerBuildNative builder stage to use Java 21 GraalVM image#15580
jamesfredley merged 2 commits into8.0.xfrom
fix/forge-graalvm-builder-java21

Conversation

@jamesfredley
Copy link
Copy Markdown
Contributor

Summary

  • PR #15579 fixed the Forge prev-snapshot GCP deploy runtime stage image, but the very next workflow run on the merge commit still fails :grails-forge-web-netty:dockerBuildNative (and the analytics variant) with UnsupportedClassVersionError: class file version 65.0.
  • Root cause: on Micronaut Gradle Plugin 3.7.10 (our pinned micronautApplicationPluginVersion), baseImage(...) on dockerfileNative only controls the final runtime stage of the generated multi-stage Dockerfile. The builder stage (FROM ... AS graalvm), where native-image actually compiles our Java 21 bytecode, is controlled by the separate graalImage property. Its default clamps jdkVersion against SUPPORTED_JAVA_VERSIONS = [17, 11] and falls back to ghcr.io/graalvm/native-image:ol7-java17-22.3.2.
  • Explicitly set graalImage on dockerfileNative to the same Java 21 image in both grails-forge-web-netty/build.gradle and grails-forge-analytics-postgres/build.gradle. Both stages of the generated Dockerfile now run on Java 21.

Failure excerpt

Step 1/13 : FROM ghcr.io/graalvm/native-image:ol7-java17-22.3.2 AS graalvm
...
Fatal error: java.lang.UnsupportedClassVersionError: org/grails/forge/netty/Application
  has been compiled by a more recent version of the Java Runtime (class file version 65.0),
  this version of the Java Runtime only recognizes class file versions up to 61.0
Error: Image build request failed with exit status 1
> Task :grails-forge-web-netty:dockerBuildNative FAILED

Full log: https://github.com/apache/grails-core/actions/runs/24518601227/job/71673053922

Why the previous fix was insufficient

Reading NativeImageDockerfile.java at tag v3.7.10:

  • Builder stage is emitted via from(new From(getGraalImage().get()).withStage("graalvm")).
  • graalImage convention: getGraalVersion().zip(getJdkVersion(), NativeImageDockerfile::toGraalVMBaseImageName) -> ghcr.io/graalvm/native-image:ol7-${jdkVersion}-${graalVersion}.
  • jdkVersion auto-derives from the toolchain but passes through toSupportedJavaVersion, which is backed by SUPPORTED_JAVA_VERSIONS = Arrays.asList(17, 11). Java 21 therefore silently becomes java17.
  • baseImage(...) only calls getBaseImage().set(imageName), and BaseImageForBuildStrategyResolver.resolve() returns that value for the final from(baseImageProvider) (runtime stage) only.

So the PR #15579 override reaches only the runtime FROM. The builder FROM ... AS graalvm was still ghcr.io/graalvm/native-image:ol7-java17-22.3.2, which rejects class file 65.0.

Verification

  • Cannot exercise dockerBuildNative locally in this environment (no Docker), but the change is a pure DSL override that takes precedence over the plugin's Java-17-only convention. The image tag matches the one already chosen for the runtime stage in Fix forge dockerBuildNative by using a Java 21 GraalVM base image #15579.
  • No application code changes; only the GraalVM builder container reference is added, next to the existing runtime container reference.

Longer-term follow-up

micronautApplicationPluginVersion=3.7.10 (March 2023) is the root reason we need this manual override: plugin 4.x adds first-class Java 21 and native-image-community support, so graalImage would be derived correctly without a hard-coded string. Bumping the plugin would be a broader change though (DSL surface shifts across 3.x -> 4.x), so this PR is intentionally minimal - just unblocking the current CI workflow.

Notes for reviewers

  • Targeting 8.0.x because that's where the deploy workflow was bumped to Java 21 in d1362be7 ("Minimum requirement for Java 21 & update to asset-pipeline to fix NPE"). 7.1.x still runs java-version: '17' in the same workflow (last run #24475218726 succeeded), so it is not affected.
  • The fix/ branch prefix should auto-label this PR as bug per .github/release-drafter.yml.

PR #15579 updated the `dockerfileNative` baseImage to
`ghcr.io/graalvm/native-image-community:21.0.2-ol9`, but the
Forge prev-snapshot GCP deploy workflow still fails with
`UnsupportedClassVersionError: class file version 65.0` because
the generated Dockerfile's builder stage continues to emit:

    Step 1/13 : FROM ghcr.io/graalvm/native-image:ol7-java17-22.3.2 AS graalvm

On Micronaut Gradle Plugin 3.7.10 (our pinned
micronautApplicationPluginVersion), `baseImage(...)` on the
`dockerfileNative` task only affects the final runtime stage of
the multi-stage Dockerfile. The builder stage (`FROM ... AS graalvm`),
where the `native-image` tool actually runs against our Java 21
bytecode, is controlled by the separate `graalImage` property.

`graalImage`'s convention is derived from `jdkVersion` via
`toGraalVMBaseImageName(graalVersion, jdkVersion)` ->
`ghcr.io/graalvm/native-image:ol7-${jdkVersion}-${graalVersion}`,
and `jdkVersion` is clamped by `NativeImageDockerfile.toSupportedJavaVersion`
against `SUPPORTED_JAVA_VERSIONS = [17, 11]`. That silently rounds
Java 21 down to Java 17, so even with the toolchain set to 21
the builder image still resolves to a Java 17 container that
rejects class file version 65.0.

Explicitly set `graalImage` to the same Java 21 community image
we use for the runtime stage so both stages of the generated
Dockerfile run on Java 21. This unblocks the failing job
`grails-forge-web-netty:dockerBuildNative` (and the analytics
module) on 8.0.x:

  https://github.com/apache/grails-core/actions/runs/24518601227

A longer-term fix is to upgrade `micronautApplicationPluginVersion`
from 3.7.10 to 4.x, which adds first-class Java 21 and
`native-image-community` support, but that is a larger change;
this two-line override unblocks CI now.

Assisted-by: claude-code:claude-opus-4-7
Copilot AI review requested due to automatic review settings April 16, 2026 17:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Forge native-image Docker build configuration so the builder stage (GraalVM/native-image compilation) uses a Java 21 GraalVM image, fixing CI failures caused by Java 17 builder images rejecting Java 21 bytecode.

Changes:

  • Set graalImage for dockerfileNative to ghcr.io/graalvm/native-image-community:21.0.2-ol9 to control the Dockerfile builder stage image.
  • Keep baseImage(...) pinned to the same Java 21 image for the final runtime stage.
  • Add inline documentation explaining Micronaut Gradle Plugin 3.7.10 behavior and why both properties are needed.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
grails-forge/grails-forge-web-netty/build.gradle Pins the native-image builder stage via graalImage to match the Java 21 runtime image.
grails-forge/grails-forge-analytics-postgres/build.gradle Same graalImage override to ensure Java 21 is used during native compilation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread grails-forge/grails-forge-web-netty/build.gradle Outdated
Comment thread grails-forge/grails-forge-analytics-postgres/build.gradle Outdated
Address Copilot review feedback on PR #15580: the
`ghcr.io/graalvm/native-image-community:21.0.2-ol9` tag was
duplicated across `graalImage` and `baseImage(...)` in both
`grails-forge-web-netty/build.gradle` and
`grails-forge-analytics-postgres/build.gradle`. If only one of
the two were updated in a future bump, the builder and runtime
stages would drift apart and the class-file-version mismatch
this fix prevents could silently reappear.

Assign the image string once to a `nativeImageContainer` local
and reuse it for both properties so the two stages can never
disagree on the GraalVM version.

Assisted-by: claude-code:claude-opus-4-7
@jamesfredley jamesfredley merged commit b7f2021 into 8.0.x Apr 16, 2026
30 checks passed
@jamesfredley jamesfredley deleted the fix/forge-graalvm-builder-java21 branch April 16, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants