4

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:

  • if I use a full JRE 9 to run the JAR file, the exception is not thrown: I double checked the modules included in the custom JRE I created with Jlink.exe, and everything is included correctly. I still want to use a smaller JRE, so I did not investigate further yet (I guess this explains why in the IDE it works perfectly)
  • I added a custom deserializer to the Gson object (see below), with which I simply manually converted the JSON string into a valid data, and that avoided the exception on the LocalDateTime class...but the exception reappeared simply on another class, this time a custom-made one.

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
il_boga
  • 1,305
  • 1
  • 13
  • 21
  • 2
    Before going into this, if you could make the project available, that would make debugging a lot easier. :) – Nicolai Parlog May 12 '20 at 17:32
  • @Nicolai I did not add more code since the issue seemed related to a generic serialization/deserialization of the LocalDateTime class, and my code seemed quite irrelevant since it uses only default settings for GSON – il_boga May 13 '20 at 08:45

2 Answers2

9

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.

Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
  • 1
    Great effort on debugging the problem +1 – Eng.Fouad May 12 '20 at 17:55
  • It worked, I just created a new JRE including `jdk.unsupported` and GSON doesn't throw any exception. In any case, the exception was thrown only for the two classes I mentioned in the question, so it was solved by two deserialziers. Should I notify GSON developers of this optional dependency? – il_boga May 13 '20 at 08:43
  • Glad I could help. Yeah opening an issue is a good idea, although there's an important details that needs to be discussed - better there than here, though. Please leave a link once the issue was opened. – Nicolai Parlog May 13 '20 at 13:18
  • Why should the dependency on `jdk.unsupported` be optional? Wouldn't a regular dependency be better because apparently without `Unsafe`, Gson's functionality is limited. – Marcono1234 May 19 '20 at 17:42
  • 1
    This should be discussed on the GSON issue. Short answer: The presence of _jdk.unsupported_ should not be relied upon (notice its name) and since GSON works without it (just not as comfortable), avoiding a strict dependency would be a good choice IMO. – Nicolai Parlog May 22 '20 at 09:02
0

I have faced the same issue when packing compose a desktop application.

update build.gradle file, add an unsupported module.

compose.desktop {
    application {
        mainClass = "MainKt"
        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "admin"
            packageVersion = "1.0.0"
            modules("java.sql")
            modules("jdk.unsupported")
        }
    }
}
Vahe Gharibyan
  • 5,277
  • 4
  • 36
  • 47