Search code examples
javajsongsonpojo

Error when trying to convert a POJO Class to Json String with GSON


I am trying to convert a POJO class to Json using Gson, but when I try to do it I get an error and I have no idea the exact solution.

My Java version is 19 and this is my class:

public class PlayerModel {
    String player;
    UUID uuid;
    TribeModel tribe;
    Inventory inventory;

    public PlayerModel(Player p, UUID uuid, Inventory inventory) {
        this.player = p.getDisplayName();
        this.uuid = uuid;
        this.tribe = new TribeModel(this);
        this.inventory = inventory;
    }

    public String getDisplayName() {
        return this.player;
    }

    public UUID getUUID() {
        return this.uuid;
    }

    public TribeModel getTribe() {
        return this.tribe;
    }

    public Inventory getInventory() {
        return this.inventory;
    }

    @Override
    public String toString() {
        return "PlayerModel [player="
                + player
                + ", uuid=" + uuid
                + ", tribe=" + tribe.toString()
                + ", inventory=" + inventory.toString() + "]";
    }

    public String toJson() {
        Gson gson = new Gson();
        String json = gson.toJson(this);
        return json;
    }

}

and these are the methods I use:

 public static void SavePlayerOnFirstJoin(Player player) {
        UUID uuid = player.getUniqueId();
        Document existPlayer = Database.getPlayer(player.getUniqueId());
        if (existPlayer == null) {
            try {
                Document playerDocument = MakePlayerDocument(player, uuid);
                Database.userCollection.insertOne(playerDocument);
            } catch (Exception e) {
                NoWipe.Log.severe(e.getMessage());
            }
        }
    }

    public static Document MakePlayerDocument(Player player, UUID uuid) {
        String playerModel = new PlayerModel(player, uuid, player.getInventory()).toJson();
        Document parse = Document.parse(playerModel);
        return parse;
    }

and this is my complete log of the error:

com.google.gson.JsonIOException: Failed making field 'java.lang.ThreadLocal#threadLocalHashCode' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
        at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:38) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:287) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.getAdapter(Gson.java:546) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:295) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.getAdapter(Gson.java:546) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:295) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.getAdapter(Gson.java:546) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:295) ~[gson-2.10.jar:?]
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.getAdapter(Gson.java:546) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.toJson(Gson.java:817) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.toJson(Gson.java:795) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.toJson(Gson.java:742) ~[gson-2.10.jar:?]
        at com.google.gson.Gson.toJson(Gson.java:719) ~[gson-2.10.jar:?]
        at me.winflix.nowipe.models.PlayerModel.toJson(PlayerModel.java:50) ~[NoWipe.jar:?]
        at me.winflix.nowipe.utils.GlobalUtils.MakePlayerDocument(GlobalUtils.java:30) ~[NoWipe.jar:?]
        at me.winflix.nowipe.utils.GlobalUtils.SavePlayerOnFirstJoin(GlobalUtils.java:24) ~[NoWipe.jar:?]
        at me.winflix.nowipe.events.JoinEvents.PlayerJoinEvent(JoinEvents.java:15) ~[NoWipe.jar:?]
        at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor51.execute(Unknown Source) ~[?:?]
        at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[purpur-api-1.19.3-R0.1-SNAPSHOT.jar:?]
        at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:80) ~[purpur-api-1.19.3-R0.1-SNAPSHOT.jar:git-Purpur-1905]
        at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[purpur-api-1.19.3-R0.1-SNAPSHOT.jar:?]
        at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:680) ~[purpur-api-1.19.3-R0.1-SNAPSHOT.jar:?]
        at net.minecraft.server.players.PlayerList.placeNewPlayer(PlayerList.java:319) ~[purpur-1.19.3.jar:git-Purpur-1905]
        at net.minecraft.server.network.ServerLoginPacketListenerImpl.placeNewPlayer(ServerLoginPacketListenerImpl.java:202) ~[?:?]
        at net.minecraft.server.network.ServerLoginPacketListenerImpl.handleAcceptedLogin(ServerLoginPacketListenerImpl.java:183) ~[?:?]
        at net.minecraft.server.network.ServerLoginPacketListenerImpl.tick(ServerLoginPacketListenerImpl.java:85) ~[?:?]
        at net.minecraft.network.Connection.tick(Connection.java:594) ~[?:?]
        at net.minecraft.server.network.ServerConnectionListener.tick(ServerConnectionListener.java:233) ~[?:?]
        at net.minecraft.server.MinecraftServer.tickChildren(MinecraftServer.java:1594) ~[purpur-1.19.3.jar:git-Purpur-1905]
        at net.minecraft.server.dedicated.DedicatedServer.tickChildren(DedicatedServer.java:482) ~[purpur-1.19.3.jar:git-Purpur-1905]
        at net.minecraft.server.MinecraftServer.tickServer(MinecraftServer.java:1424) ~[purpur-1.19.3.jar:git-Purpur-1905]
        at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1194) ~[purpur-1.19.3.jar:git-Purpur-1905]
        at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:321) ~[purpur-1.19.3.jar:git-Purpur-1905]
        at java.lang.Thread.run(Thread.java:1589) ~[?:?]
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final int java.lang.ThreadLocal.threadLocalHashCode accessible: module java.base does not "opens java.lang" to unnamed module @2e24b18c
        at java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:387) ~[?:?]
        at java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:363) ~[?:?]
        at java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:311) ~[?:?]
        at java.lang.reflect.Field.checkCanSetAccessible(Field.java:180) ~[?:?]
        at java.lang.reflect.Field.setAccessible(Field.java:174) ~[?:?]
        at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:35) ~[gson-2.10.jar:?]
        ... 40 more

I have read about creating a TypeAdapter from the POJO to Gson but I still don't fully understand how to do it and thanks to everyone :)

I also tried with Jackson but it gives me another huge error that if necessary I insert it in this post :)


Solution

  • As mentioned in the comments your code indirectly tries to serialize a java.lang.ThreadLocal. Gson has no built-in adapter for that class so it tries to use reflection, which fails because internal fields of JDK classes cannot be accessed using reflection, see also the Gson troubleshooting guide.

    The field of type ThreadLocal is not directly in your PlayerModel class but nested, either in TribeModel or Inventory (or nested in some of the classes of their fields). Depending on your use case, you could either:

    • check if the ThreadLocal field (or even its declaring class) really has to be serialized and deserialized, and if not exclude it, or
    • write a custom TypeAdapter which serializes and deserializes ThreadLocal instances. For example by calling their get() method to obtain the value during serialization, respectively creating a new instance and calling set(...) to set the value during deserialization.