hello there. Am using Gson and am implementing my...
# ktor
d
hello there. Am using Gson and am implementing my own custom serializers specifically for the Instant class but am getting this error. 2025-04-21 091355.340 [eventLoopGroupProxy-4-1] ERROR i.k.server.application.Application - Error occurred: JsonIOException || Failed making field 'java.time.Instant#seconds' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type. See https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible com.google.gson.JsonIOException: Failed making field 'java.time.Instant#seconds' accessib. let me share the impl
Copy code
package com.root.plugins


import com.google.gson.*
import com.google.gson.FieldNamingPolicy
import com.google.gson.Strictness
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import com.root.utility.UtilityFunctions
import io.ktor.http.ContentType
import io.ktor.serialization.gson.*
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.Encoder
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import kotlinx.serialization.modules.SerializersModule
import java.lang.reflect.Type
import java.time.Instant
import kotlinx.datetime.Instant as KtxInstant

@OptIn(ExperimentalSerializationApi::class)
fun Application.configureSerialization() {
    // Create the fully configured Gson instance
    val gson = GsonBuilder()
        .setPrettyPrinting()
        .setStrictness(Strictness.LENIENT)
        .serializeNulls()
        .registerTypeAdapter(UtilityFunctions.ApiResponseGsonSerializer::class.java, UtilityFunctions.ApiResponseGsonSerializer())
        .registerTypeAdapter(Instant::class.java, object : JsonSerializer<Instant>, JsonDeserializer<Instant> {
            override fun serialize(src: Instant, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
                return JsonPrimitive(src.toString())
            }

            override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Instant {
                return Instant.parse(json.asString)
            }
        })
        .registerTypeAdapter(Instant::class.java, object : TypeAdapter<Instant>() {
            override fun write(out: JsonWriter, value: Instant?) {
                if (value == null) {
                    out.nullValue()
                } else {
                    out.value(value.toString()) // ISO-8601
                }
            }

            override fun read(reader: JsonReader): Instant? {
                return if (reader.peek() == JsonToken.NULL) {
                    reader.nextNull(); null
                } else {
                    Instant.parse(reader.nextString())
                }
            }
        }.nullSafe())
        .registerTypeAdapter(KtxInstant::class.java, object : JsonSerializer<KtxInstant>, JsonDeserializer<KtxInstant> {
            override fun serialize(src: KtxInstant, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
                return JsonPrimitive(src.toString())
            }

            override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): KtxInstant {
                return KtxInstant.parse(json.asString)
            }
        })
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
        .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
        .create()

    install(ContentNegotiation) {
        // Apply the pre-configured Gson instance
            register(ContentType.Application.Json, GsonConverter(gson))
        

        json(Json {
            isLenient = true
            prettyPrint = true
            encodeDefaults = true
            ignoreUnknownKeys = true
            coerceInputValues = true
            namingStrategy = JsonNamingStrategy.SnakeCase
            serializersModule = SerializersModule {
                contextual(KtxInstant::class) {
                    KotlinxInstantSerializer
                }
            }
        })
    }

    routing {
        get("/json/kotlinx-serialization") {
            call.respond(mapOf("hello" to "world"))
        }
        get("/json/gson") {
            call.respond(mapOf("hello" to "world"))
        }
    }
}

// And define your serializer
object KotlinxInstantSerializer : KSerializer<KtxInstant> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("KotlinxInstant", PrimitiveKind.STRING)

    override fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: KtxInstant) {
        encoder.encodeString(value.toString())
    }

    override fun deserialize(decoder: Decoder): KtxInstant {
        return KtxInstant.parse(decoder.decodeString())
    }
}
then this is the full error log
Copy code
2025-04-21 09:13:55.340 [eventLoopGroupProxy-4-1] ERROR i.k.server.application.Application - Error occurred: JsonIOException || Failed making field 'java.time.Instant#seconds' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
See <https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible> 
com.google.gson.JsonIOException: Failed making field 'java.time.Instant#seconds' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
See <https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible>
        at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:76)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:388)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:161)
        at com.google.gson.Gson.getAdapter(Gson.java:628)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:201)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:395)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:161)
        at com.google.gson.Gson.getAdapter(Gson.java:628)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:201)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:395)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:161)
        at com.google.gson.Gson.getAdapter(Gson.java:628)
        at com.google.gson.Gson.toJson(Gson.java:928)
        at com.google.gson.Gson.toJsonTree(Gson.java:802)
        at com.google.gson.Gson.toJsonTree(Gson.java:779)
        at com.root.routing.LeasesRoutesKt.handleLeaseCreation(LeasesRoutes.kt:53)
        at com.root.routing.LeasesRoutesKt.access$handleLeaseCreation(LeasesRoutes.kt:1)
        at com.root.routing.LeasesRoutesKt$handleLeaseCreation$1.invokeSuspend(LeasesRoutes.kt)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.internal.DispatchedContinuation.resumeWith(DispatchedContinuation.kt:197)
        at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:146)
        at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:120)
        at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:11)
        at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:70)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
        at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final long java.time.Instant.seconds accessible: module java.base does not "opens java.time" to unnamed module @10315254
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
        at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:68)
What could be the issue. please assist.
đź§µ 4
h
Any reason to use Gson and kotlinx-serialization in the same code? Gson is broken by design, it uses reflection and requires Java beans by default. While you can pass the deserializer by yourself, what you did (in theory but failed), why don’t you just use kotlinx-serialization with builtin kotlinx.datetime.Instant support (and soon kotlin.time.Instant)?
And you don’t need to use a typeadapter and a deserializer in this case, but it should work though.
d
You're right—there's no strong reason to use both. My implementation was originally based entirely on Gson, but I added
kotlinx-serialization
later on after running into issues with Gson. Thanks for the guidance! I’ll give
kotlinx-serialization
a proper try now.
p
Next it being a Java library, I think Gson is also in maintenance mode. Meaning no new big features are added and no active development is done on it I think. Gson is currently in maintenance mode; existing bugs will be fixed, but large new features will likely not be added. If you want to add a new feature, please first search for existing GitHub issues, or create a new one to discuss the feature and get feedback.
d
This is well noted, Thank you for the information @Pim