jsongsonjava-9jlinkjdeps

Unexpected behaviour from Gson


I developed a small application that stores data coming from a device: I chose to store data in JSON format, and the serialization/deserialization of the data works just fine, even if it involves some custom types created by me...but only I work in the IDE (Eclipse, for that matter).

When I export a runnable JAR file though, the deserialization of the data encounters some kind of problem, because the software always throws this exception:

Caused by: java.lang.UnsupportedOperationException: Cannot allocate class LocalDateTime
    at com.google.gson.internal.UnsafeAllocator$4.newInstance(UnsafeAllocator.java:104)
    at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:225)
    ... 88 common frames omitted

I thought I'd encounter problems with custom types, not a built-in one. At this point, I discovered two things:

At this point, I guess I can simply add a deserializer for each data type that causes problem, but I'm wondering why the issue won't happen with a full JRE, and why a smaller JRE causes this, even if all the modules required are included. Maybe it's worth mentioning also that I added no custom serializer to the Gson object that saves the data, it is all serialized as per Gson default.

LocalDateTime deserializer:

    @Override
    public LocalDateTime deserialize(JsonElement json, java.lang.reflect.Type type,
                JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {

        JsonObject joDate = json.getAsJsonObject().get("date").getAsJsonObject();
        JsonObject joTime = json.getAsJsonObject().get("time").getAsJsonObject();
        //JSON example: {"date":{"year":2019,"month":1,"day":9},"time":{"hour":6,"minute":14,"second":1,"nano":0}
        return LocalDateTime.of(joDate.get("year").getAsInt(),
                joDate.get("month").getAsInt(),
                joDate.get("day").getAsInt(),
                joTime.get("hour").getAsInt(),
                joTime.get("minute").getAsInt(),
                joTime.get("second").getAsInt(),
                joTime.get("nano").getAsInt());
    }
}

Jdeps.deps modules list:

com.google.gson
java.base
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
org.slf4j

After the answer I received, I opened an issue here.


Solution

  • TL;DR

    You need a runtime image (e.g. full JDK or something built with jlink) that includes the module jdk.unsupported.

    Full Answer

    GSON wants to create instances of classes it deserializes without calling any constructors (so nothing gets initialized without GSON saying so). This can't normally be done, but sun.misc.Unsafe offers a way to do this with the method allocateInstance. To that end, GSON needs an instance of sun.misc.Unsafe. The topmost frame in the call stack is from UnsafeAllocator, which uses common trickery to get Unsafe.

    The problem is, sun.misc.Unsafe is in module jdk.unsupported, which is present in a full JDK but you won't usually find in runtime images.

    When creating your runtime image with jlink, make sure to include the option --add-modules jdk.unsupported and you should be good to go.

    Arguably, GSON should declare an optional dependency on jdk.unsupported with requires static.