kotlin · performance · postgresql · jsonb · 4 min de lecture

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

3 minutes pour démarrer Spring Boot en local : debug d'un problème de performance causé par une sealed class Kotlin et Jackson.

Aussi disponible enEnglish
Hugo
Hugo27 oct. 2025 · 4 min
Quand une sealed class Kotlin fait exploser le temps de démarrage de Spring Boot (de 6s à 3 minutes)

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

text
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.

kotlin
@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 :

kotlin
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 :

kotlin
@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 :

kotlin
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.

Hugo

Écrit par

Hugo

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

Vous avez aimé ce récit ?

Environ un mail par mois, quand un texte mérite d'être écrit. Vos lecteurs restent vos lecteurs.

205 abonnés · désabonnement en un clic

À lire aussi

Less is more : J'ai eu beaucoup de crash mémoire sur mon application

Tout se passait bien et puis, d'un coup, il y a 2 semaines j'ai commencé à avoir des problèmes de crash de l'application...

15 juil. 2024
Less is more : J'ai eu beaucoup de crash mémoire sur mon application

Spring Boot : dans le doute reboote

[![spring-boot-logo](/images/28307-spring-boot-logo.png)](http://eventuallycoding.com/wp-content/uploads/2014/07/28307-spring-boot-logo.png)Le dernier...

16 juil. 2014
Spring Boot : dans le doute reboote

Vers l'infini et au-delà

Les performances de votre site web sont-elles prêtes à encaisser un pic de charge ?

22 déc. 2015
Vers l'infini et au-delà

Réponses

0 Commentaire

Pas encore de commentaire. Soyez le premier !