When a Kotlin Sealed Class Explodes Your Spring Boot Startup Time (from 6s to 3 minutes)

By Hugo LassiègeOct 27, 20253 min read

Yesterday, I was working on a new application (more details coming very soon), and my Spring Boot app was starting up in just a few seconds. Then suddenly, disaster struck: 3 minutes to start.

It's pretty common to see gradual startup time degradation as you add features to an application (Quartz initialization, new beans to instrument, etc.), but this was way beyond acceptable drift.

Spoiler: the culprit was a Kotlin sealed class.

The Symptom

Started MyApplicationKt in 186.641 seconds

186 seconds to start a Spring Boot app locally. That's far from normal. At first, I suspected open database connections creating locks, but I quickly ruled that out. Going back a few commits brought everything back to normal. The culprit was probably two JPA entities with JSONB columns I had added.

Turns out I rarely use PostgreSQL's JSON functionality because in most cases, you can avoid it.

But here, I needed to store dynamic configuration for settings.

@Entity
class Theme(
    @JdbcTypeCode(SqlTypes.JSON)
    @Column(columnDefinition = "jsonb")
    val schema: ThemeSchema,
    // ...
)

I tested several things to check if a specific JPA/Hibernate mechanism was the cause:

Disabling ddl-auto → No effect Switching to validate then none → Still 3 minutes Removing Quartz (which I wasn't using, leftover code from hakanai.io) → Nothing

Then I tried replacing ThemeSchema with a simple Map<String, String>. This change brought startup time back down to under 6 seconds.

The problem? ThemeSchema contained a sealed class:

data class ThemeSchema(
    val settings: Map<String, SettingDefinition>
)

sealed class SettingDefinition {
    abstract val label: String
    abstract val default: Any
    abstract val category: String
}

data class SelectSetting(...) : SettingDefinition()
data class ColorSetting(...) : SettingDefinition()
data class BooleanSetting(...) : SettingDefinition()

Why is this slow? When Hibernate + Jackson discover this structure at startup:

  • Jackson needs to scan the classpath to find all subclasses of SettingDefinition
  • It needs to figure out how to serialize/deserialize polymorphism
  • Without explicit annotations, it has to try multiple strategies
  • All of this using heavy reflection

All this scanning and reflection usage is expensive.

The Solution

Explicitly tell Jackson how to handle polymorphism:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
)
@JsonSubTypes(
    JsonSubTypes.Type(value = SelectSetting::class, name = "select"),
    JsonSubTypes.Type(value = ColorSetting::class, name = "color"),
    JsonSubTypes.Type(value = BooleanSetting::class, name = "boolean")
)
sealed class SettingDefinition {
    // ...
}

With these annotations, Jackson no longer needs to scan: we explicitly tell it which subclasses exist and how to differentiate them in JSON.

Lessons Learned

Several takeaways, some I already knew, others I learned:

  • making atomic commits makes it easy to identify the responsible commit(s) (reducing feedback loops, though I'd hope this has become an industry standard by now)
  • performance issues are always approached the same way: hypothesis, test, measure, validate, etc. (I'm used to that one)
  • Murphy's Law: whenever I touch something I'm not familiar with, I break something ^^

Here Murphy's Law was JSONB + Kotlin sealed classes + Jackson don't play well together by default. You need to explicitly define polymorphism with @JsonTypeInfo.

It might have been possible to do it differently, but less type-safe:

data class SettingDefinition(
    val type: String, // "select", "color", "boolean"
    val label: String,
    val default: Any,
    val category: String,
    val options: List<SelectOption>? = null
)

No sealed class, no polymorphism, no problem. But I would have lost Kotlin's pattern matching and compiler guarantees.

Anyway, if you landed on this post because of a performance issue, take a close look at how you're helping Jackson understand how to serialize/deserialize your schema.


Share this:

Written by Hugo Lassiège

Software Engineer with more than 20 years of experience. I love to share about technologies and startups

Copyright © 2025
 Eventuallymaking
  Powered by Bloggrify