Quand une sealed class Kotlin fait exploser le temps de démarrage de Spring Boot (de 6s à 3 minutes)

By Hugo LassiègeOct 27, 20254 min read

Quand une sealed class Kotlin fait exploser le temps de démarrage de Spring Boot (de 6s à 3 minutes)

Hier, je travaillais sur une nouvelle application, plus d'infos là-dessus dans très peu de temps, mon application Spring Boot démarrait en quelques secondes. Et soudain, c'est le drame : 3 minutes pour démarrer.

C'est assez fréquent d'avoir des petites dérives sur les temps de démarrage au fur et à mesure de l'ajout de fonctionnalités dans une application (l'initialisation de Quartz, des nouveaux beans à instrumenter etc...) mais là on est loin d'une dérive acceptable.

Spoiler : le coupable était une sealed class Kotlin.

Le symptôme

Started MyApplicationKt in 186.641 seconds

186 secondes pour démarrer une app Spring Boot en local. C'est loin d'être normal. Si j'ai au début soupconné des connexions ouverte sur la base de données créant un lock en base, j'ai rapidement éliminé cette hypothèse. Et en revenant quelques commits en arrière, tout est revenu à la normale. Le coupable était donc probablement deux entités JPA avec des colonnes JSONB que j'avais rajoutées.

Il se trouve que j'utilise très rarement la fonctionnalité Json avec Postgresql parce que dans la grande majorité des cas, on peut s'en passer.

Mais ici, il s'agissait de stocker une configuration dynamique pour du paramétrage.

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

J'ai testé plusieurs choses pour vérifier si un mécanisme particulier de JPA/hibernate pouvait être en cause :

Désactiver ddl-auto → Aucun effet Passer en validate puis none → Toujours 3 minutes Supprimer Quartz (que j'utilisais pas, c'est un reliquat du code que j'ai repris de hakanai.io) → Rien

Et puis j'ai tenté de remplacer ThemeSchema par un simple Map<String, String>. Cette modification a fait repasser le démarrage à moins de 6 secondes.

Le problème ? ThemeSchema contenait une 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()

Pourquoi c'est lent ? Quand Hibernate + Jackson découvrent cette structure au démarrage :

Jackson doit scanner le classpath pour trouver toutes les sous-classes de SettingDefinition. Il doit comprendre comment sérialiser/désérialiser du polymorphisme. Sans annotations explicites, il doit tester plusieurs stratégies. Tout ça en utilisant de la reflection de façon intensive.

Tout ce scanning et cet usage de la réflexion coûte cher.

La solution

Dire explicitement à Jackson comment gérer le polymorphisme :

@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 {
    // ...
}

Avec ces annotations, Jackson n'a plus besoin de scanner : on lui dit explicitement quelles sont les sous-classes et comment les différencier dans le JSON.

Les leçons

Plusieurs choses à retenir, certaines leçons que je connaissais déjà, d'autres que j'ai appris :

  • faire des commits unitaires permet de facilement identifier le/les commits responsables (diminuer les temps de rétroaction, mais ça je doute que ce soit pas devenu une norme dans l'industrie)
  • les problèmes de perf s'approchent toujours de la même facon : hypothèse, test, mesure, validation etc... (pour le coup ça, j'ai l'habitude)
  • la loi de Murphy : dès que je touche à un truc dont j'ai pas l'habitude, je casse quelque chose ^^

Ici la loi de Murphy c'était JSONB + les sealed classes Kotlin + Jackson qui ne font pas bon ménage par défaut. Il faut expliciter le polymorphisme avec @JsonTypeInfo.

Il aurait peut-être été possible de faire autrement, mais moins 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
)

Pas de sealed class, pas de polymorphisme, pas de problème. Mais j'aurais perdu le pattern matching de Kotlin et la garantie du compilateur.

Bref, si jamais vous tombez sur ce billet suite à un problème de performance chez vous, regardez bien comment vous aidez Jackson à savoir comment serialiser/déserialiser votre schéma.


Share this:

Written by Hugo Lassiège

Software engineer, ex-freelance, ex-cofounder, ex-CTO. I love building things, sharing knowledge and helping others.

Related Articles

You might also be interested in these articles

La stack technique pour construire un SAAS en 1 semaine

La stack technique pour construire un SAAS en 1 semaine

Quelle stack pour construire un SAAS en 1 semaine ?

kotlinpostgresql
Implémenter un système d'abonnement avec Stripe pour un SAAS

Implémenter un système d'abonnement avec Stripe pour un SAAS

Comment mettre en place un système de paiement pour un SAAS par abonnement avec Stripe

kotlin
Connecteur Mongo Postgresql

Connecteur Mongo Postgresql

[![47f0a20a1f9df18484d7972fa41472b8[1]](/images/aa1fc-47f0a20a1f9df18484d7972fa41472b81.jpg)](http://eventuallycoding.com/wp-content/uploads/2016/03/a...

postgresql
Copyright © 2026
 Eventuallycoding
  •
Powered by Bloggrify