javaspring-bootdockerpaketo

Add-Opens Entry in Java MANIFEST.MF Doesn't Work In Docker Image Built by Spring Boot Plugin


I've got a Spring Boot application (V3 + JDK 17) that prints a warning to open some modules (because of JRuby):

2023-07-14T11:13:35.651Z [main] WARN FilenoUtil : Native subprocess control requires open access to the JDK IO subsystem
Pass '--add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED' to enable.

Now passing the suggested params (either as command-line args or via JDK_JAVA_OPTIONS env var) removes the warning.
I also learned that the same can be achieved by adding the modules in an Add-Opens entry:

// build.gradle.kts

bootJar {
  requiresUnpack("**/asciidoctorj-*.jar")

  manifest {
    attributes["Add-Opens"] = "java.base/java.io java.base/sun.nio.ch"
  }
}

And I can confirm that the final fat JAR file created by Spring Boot plugin does indeed have a MANIFEST.MF with the expected entry:

Manifest-Version: 1.0
Add-Opens: java.base/java.io java.base/sun.nio.ch
Main-Class: org.springframework.boot.loader.JarLauncher
# Other entries!!!

And when I run the JAR file (using java -jar) I can see the warning is gone.

However, when I deploy the docker image built by gradle bootBuildImage (default configuration, nothing customized), I still see the warning. I checked the MANIFEST.MF file in the container and the expected entry exists.

Does anyone know why it doesn't work? Or generally what's the right way to get rid of the warning?

Update
The short answer is "Add-Opens manifest entries are only supported when launching the JVM using java -jar." (see this and this).


Solution

  • Does anyone know why it doesn't work? Or generally what's the right way to get rid of the warning?

    My suspicion is that it's because the generated container image does not actually contain the JAR, it contains the exploded JAR files.

    When you build with the Spring Boot Gradle plugin, roughly the following steps happen:

    Outside of the container:

    1. Gradle runs, it compiles your code and produces a JAR
    2. The Spring Boot plugin downloads images & sets up the container build environment
    3. The Spring Boot plugin provides the contents of your JAR to the container environment (not the JAR, but what's in the JAR)
    4. The Spring Boot plugin starts the container build
    5. The buildpacks run and generate an image.

    Because the container build process gets the contents of the JAR file, not an actual JAR the start command is not java -jar, it's java -cp .. your.Main. I suspect that is why Java is not honoring the value in the MANIFEST.MF file.


    The easy fix is to just set the arguments like you've been doing either as an argument or in the environment variable JDK_JAVA_OPTIONS.

    I think you can also use JAVA_TOOL_OPTIONS they should both work here.

    Besides this, you can make buildpacks configure your application to run using java -jar but you would need to make a couple of changes.

    1. Build using pack build instead of the Spring Boot plugin. Spring Boot plugin will only build the JAR outside of the container. With pack build, it will by default build from your source so the Java compilation happens inside the build container.

    2. If you pack build, you can then set BP_GRADLE_BUILT_ARTIFACT and have the pattern match multiple files including your JAR. The buildpack will carry those files over into final image, but more importantly it will NOT explode the JAR file (that only happens when BP_GRADLE_BUILT_ARTIFACT matches a single JAR file).

    If $BP_GRADLE_BUILT_ARTIFACT matched a directory or multiple files Restores the files matched by $BP_GRADLE_BUILT_ARTIFACT to <APPLICATION_ROOT>

    1. The executable JAR buildpack will then run, find your JAR file and it should configure the container image to run using java -jar your-executable.jar.

    I would recommend one of the other options, but if you really want to configure through the MANIFEST.mf file, you should be able to make that work.