2

I am using a VSCode devcontainer to write a Java application. I put it down for about a month, came back to work on it and now I'm getting some unfamiliar errors.

Configuration

Here I'll provide my relevant configuration files for my devcontainer environment.

My Dockerfile is as below:

FROM openjdk:16-slim-buster

# Install things
RUN apt-get update \
    && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
    #
    # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed
    && apt-get -y install curl git openssh-client vim less iproute2 procps lsb-release

# Install Maven CLI things
ARG MAVEN_VERSION=3.6.3
ARG MAVEN_SHA=c35a1803a6e70a126e80b2b3ae33eed961f83ed74d18fcd16909b2d44d7dada3203f1ffe726c17ef8dcca2dcaa9fca676987befeadc9b9f759967a8cb77181c0
RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
  && curl -fsSL -o /tmp/apache-maven.tar.gz https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
  && echo "${MAVEN_SHA} /tmp/apache-maven.tar.gz" | sha512sum -c - \
  && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
  && rm -f /tmp/apache-maven.tar.gz \
  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG /root/.m2

# Allow for a consistant java home location for settings - image is changing over time
RUN if [ ! -d "/docker-java-home" ]; then ln -s "${JAVA_HOME}" /docker-java-home; fi

And my devontainer JSON is here:

{
    "name": "Java Development",
    "dockerFile": "Dockerfile",

    // Set *default* container specific settings.json values on container create.
    "settings": { 
        "terminal.integrated.shell.linux": "/bin/bash",
        "java.home": "/docker-java-home"
    },
    
    // Add the IDs of extensions you want installed when the container is created.
    "extensions": [
        "vscjava.vscode-java-pack",
        "redhat.vscode-xml"
    ]
}

Problem

When I had previously stopped working on this app, everything compiled and ran just fine and I was able to launch my configurations through VSCode for testing. Now, I'm running into two issues, and I think they're related. First, when I try to run my application using a launch configuration, it does not work and I get this strange error:

Strange java.base error

So I can't launch anything through VSCode. Next, I tried a mvn package and my tests failed with errors such as below:

com.google.common.util.concurrent.UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item
        at com.insurexcel.poipoi.input.InputProcessorTest.setup(InputProcessorTest.java:34)
Caused by: java.lang.IllegalStateException: Unable to load cache item
        at com.insurexcel.poipoi.input.InputProcessorTest.setup(InputProcessorTest.java:34)
Caused by: java.lang.ExceptionInInitializerError
        at com.insurexcel.poipoi.input.InputProcessorTest.setup(InputProcessorTest.java:34)
Caused by: com.google.inject.internal.cglib.core.$CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @523fa30b
        at com.insurexcel.poipoi.input.InputProcessorTest.setup(InputProcessorTest.java:34)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @523fa30b
        at com.insurexcel.poipoi.input.InputProcessorTest.setup(InputProcessorTest.java:34)

So it looks related to some reflection. I'm using GSON and Guice, here's the relevant part of the pom.xml:

        <!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.inject/guice -->
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>4.2.3</version>
        </dependency>

I tried updating Guice to 5.0.0-BETA-1 and that fixed the errors in the tests and made things compile successfully. However, now I get runtime errors when doing some serialization. Here's what the errors look like:

Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String java.lang.Throwable.detailMessage accessible: module java.base does not "opens java.lang" to unnamed module @523fa30b
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:177)
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:171)
        at com.google.gson.internal.reflect.UnsafeReflectionAccessor.makeAccessible(UnsafeReflectionAccessor.java:44)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:159)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
        at com.google.gson.Gson.getAdapter(Gson.java:458)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
        at com.google.gson.Gson.getAdapter(Gson.java:458)
        at com.google.gson.Gson.toJson(Gson.java:696)
        at com.google.gson.Gson.toJson(Gson.java:683)
        at com.google.gson.Gson.toJson(Gson.java:638)
        at com.google.gson.Gson.toJson(Gson.java:618)
        ...

So obviously it's related to GSON. It appears unable to access a private field? The object I'm attempting to serialize looks like this:

public class MapContainer {
    private MyMapObject map;
    private String status;
    private Exception errorInformation;

    public MapContainer(MyMapObject map) {
        this.map = map;
        this.status = "okay";
        this.errorInformation = null;
    }
    public MapContainer(Exception error) {
        this.map = null;
        this.status = "error";
        this.errorInformation = error;
    }

    ...
    getters and setters
    ...
}

And based on the error, it appears to be having a hard time with the Exception field in my object. I've looked all over and can't really find a good reason that this may be happening. I checked the Docker image I'm running this in and it does appear to have been updated recently. My java --version in the container returns this:

openjdk 16-ea 2021-03-16
OpenJDK Runtime Environment (build 16-ea+30-2130)
OpenJDK 64-Bit Server VM (build 16-ea+30-2130, mixed mode, sharing)

If anybody can provide any clues or ideas as to where this may be coming from I would greatly appreciate it!

Finley
  • 392
  • 1
  • 8
  • 18

1 Answers1

4

The problem is Gson is attempting to use reflection to access a private field of some class in the java.lang module.

In the days before Java 9, this was fine. With Java 9 this can throw exceptions.

The quick and dirty workaround would be to use an -add-opens option to the java command line. See the first reference for more information.

Another option would be to implement a custom object mapper to serialize / deserialize the JDK class (or classes) that triggering this. It is a bit dodgy for your application's serialization / deserialization to depend on the details of private fields of JDK classes. They may change, causing your application to break without warning.

(My guess is that this is caused by the errorInformation field ...)

For more information about these options, see:


I checked the Docker image I'm running this in and it does appear to have been updated recently.

Yes. It looks like you are using the newly Java 16 EA release now. I'm not sure this is advisable. It is certainly inadvisable to have your docker image updated under your feet. You should be developing against a specific target (major) version of Java.

UPDATE

However, for this problem to have "suddenly" started happening due to Java version change in your container, the previous version must have been Java 8 or earlier.

On reviewing the Java 16 page on the OpenJDK site, I see that it is implementing JEP 396: Strongly Encapsulate JDK Internals by Default which would have the effect of stopping Gson from messing with the access of private fields. If you read the JEP there may be another workaround.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216