lundi 29 décembre 2025

Migration d’une application Spring Boot 2.2 vers Spring Boot 4.0 : retour d’expérience concret

 En 2019, nous avons développé une application métier basée sur :

  • Java 8
  • Spring Boot 2.2.7
  • Gradle 6.6.1
  • Thymeleaf
  • Bootstrap 4.3.1
  • Environ 40 000 lignes de code Java

Fin 2025, voici un retour d’expérience concret sur la migration vers :

  • Java 25
  • Spring Boot 4.0.1
  • Gradle 9.2.1

La partie thymeleaf, n'a pas été touché, il y a quelques warning concernant l'obsolescence de certain commande tel que th:include.

Une migration plus rapide que prévu

Un point important : en moins de 30 minutes, l’application démarrait sur la nouvelle stack.

Évidemment, "ça démarre" ne veut pas dire "tout fonctionne", mais ce premier jalon est très révélateur :

  • la base Spring Boot reste solide
  • les outils de migration sont matures
  • la dette technique était maîtrisée

Les changements majeurs rencontrés

De javax.* à jakarta.*

C’est probablement le changement le plus visible.

  • Tous les imports javax.* doivent être migrés vers jakarta.*

Impact sur :

  • JPA
  • Validation
  • Servlet API
  • Sécurité

Rien de compliqué, mais beaucoup de petites modifications réparties dans le code.

Hibernate : requêtes et comportements modifiés

C’est ici que se trouvent les changements les plus subtils.

Le QueryHint hint_pass_distinct_through ne fonctionne plus

Certaines optimisations Hibernate basées sur ce hint ne sont plus supportées ou ont changé de comportement.

Résultat :

  • des requêtes qui fonctionnaient avant produisent maintenant des résultats différents

  • nécessité de revoir certaines stratégies de DISTINCT côté JPQL ou Criteria

Séquences de base de données

Certaines séquences ont dû être modifiées :

  • stratégies de génération (allocationSize, sequenceName)

  • compatibilité avec les nouvelles versions Hibernate

  • comportement différent selon le dialecte

Ce point est critique sur des applications avec beaucoup d’écritures en base.

Pour avoir effectuer énormément de migration de spring boot, je dirais que souvent c'est dans les requêtes que souvent qu'il y a des problèmes. Hibernate change beaucoup, une rêquetes qui passait ne fonctionne pu... mais fonctionne pourtant encore avec d'autre ORM. Maintenant j'évite d'écrire des requêtes dynamiques en employant les Specification ou JPQL. Je préfère opter pour du natif.


Dépréciations : beaucoup de nettoyage

La migration a mis en lumière :

  • un grand nombre d’API : jpa, sécurité dépréciées
  • des patterns devenus obsolètes

Résultat positif : un code plus propre, plus explicite et plus aligné avec les standards actuels.


Bilan global

Migration faisable sur une base de code de petite taille en moins d'une journée.

Temps de démarrage rapide, même avant corrections

Écosystème Spring mature malgré les changements majeurs

Convention over configuration a permis de diminuer le temps de migration. 

Effort réel sur :

  • Hibernate
  • JPA Criteria / Specifications
  • Sécurité
  • Jakarta

mardi 16 décembre 2025

Comment les annotations façonnent le code en Java

 

Introduction : L'évolution de la métaprogrammation en Java

Traditionnellement, l'inspection des métadonnées (comme les annotations) se faisait au runtime via la Réflexion. Approche puissante mais qui introduit des pénalités de performance et des risques d'erreurs cachées. L'approche moderne en Java est la Génération de Code à la Compilation, notamment via l'Annotation Processing , qui permet de transformer les métadonnées en classes sources compilées.

J'ai ajouté cette possibilité à la librarie Formatic pour la gestion des formulaires par cette transition, révélant clairement les forces et faiblesses des deux modèles.

1. Le Modèle Traditionnel : Réflexion (Runtime)

Avant l'Annotation Processing, nos gestionnaires (TextInputHandler, SelectInputHandler, etc.) utilisaient la réflexion pour lire les annotations lors de l'exécution de l'application et construire les objets FormFieldMetadata.

Avantages de la Réflexion :

  • Simplicité de configuration : Pas besoin d'outillage spécialisé (comme un Processor et JavaPoet).
  • Flexibilité : Idéale pour les besoins très dynamiques ou l'intégration tardive où le contexte de la JVM est essentiel (ex: trouver un service spécifique via un conteneur IoC).
  • Débogage facile : Le code s'exécute dans l'IDE comme n'importe quelle autre classe Java.

Inconvénients de la Réflexion :

  • Pénalité de performance (Runtime) : La réflexion est notoirement lente, car elle contourne le typage fort et l'optimisation du JIT (Just-In-Time) Compiler. Pour des centaines de champs, le temps de construction des métadonnées peut devenir perceptible. Cependant, avec le cache, il est possible de diminuer considérablement la pénalité.
  • Erreurs masquées : Les fautes de frappe dans les noms de méthodes ou les attributs d'annotation (ex: optionsProvider = "getcitites" au lieu de "getCities") ne sont détectées qu'au runtime (une erreur 404 dans votre application finale).
  • Problèmes de sécurité (si applicable) : Peut être restreinte dans certains environnements sécurisés.

2. Le Nouveau modèle : annotation processing (Compile-Time)

Avec notre implémentation du FormMetadataProcessor, nous avons déplacé toute la logique de construction des métadonnées vers l'étape de compilation. L'annotation agit comme une instruction pour le compilateur de générer un nouveau fichier source (ex: EditorFormMetadata.java).

La librairie JavaPoete est utilisé pour faciliter la création de cette classe.

https://github.com/marccollin/formatic/blob/annotation_processor/formatic-core/src/main/java/com/formatic/core/processor/FormMetadataProcessor.java

Comme vous pouvez remarquer, c'est pas le code le plus aisé à lire.

Avantages de l'annotation processing :

  • Performance maximale (Runtime) : Le gain le plus significatif. La construction des métadonnées est transformée en lignes de code Java standard et fortement typées. La List<FormFieldMetadata> est créée par une simple méthode statique (souvent getMetadata()) sans aucune réflexion, ce qui la rend instantanée à l'exécution.
  • Détection d'erreurs à la compilation : Le Processor garantit la validité structurelle du code généré. Si la logique du Processor est erronée (ex: elle tente d'utiliser FormFieldType.INPUT au lieu de FormFieldType.TEXT), l'application ne compile pas, assurant une qualité et une robustesse accrues.
  • Simplicité du code client : Le framework n'a plus besoin de code complexe de handler basé sur la réflexion pour interpréter les annotations. Il appelle simplement une méthode statique de la classe générée.
  • Cache : Les outils de build (Gradle, Maven) mettent en cache les résultats du Processor, accélérant les compilations subséquentes.

Inconvénients de l'annotation processing :

  • Complexité du développement (le Processor) : Le code du FormMetadataProcessor est intrinsèquement plus difficile à écrire et à maintenir. Il nécessite la maîtrise de l'API javax.lang.model et d'outils tiers comme JavaPoet.
  • Débogage complexe : Le débogage d'un Processor nécessite une configuration spéciale et l'attachement d'un débogueur à la JVM du compilateur (souvent via un build à distance sur le port 5005).
  • Limitation des entrées : Le Processor est aveugle à tout ce qui nécessite un état de runtime (API, base de données, injection de dépendances). C'est pourquoi, pour les options dynamiques (optionsProvider), nous ne générons que le nom de la méthode, laissant la résolution des données au runtime de l'application.
  • Duplication de la logique : La logique de construction des métadonnées doit être réécrite du modèle de handler réflexif vers le modèle de génération de code JavaPoet.

Conclusion : Le choix de la performance

Bien que le coût initial de développement d'un Processor soit plus élevé, le gain en performance d'exécution est d'environ 10ms sur 1 millions d'appel. Ce choix technique pourrait être intéressant pour toute librairie où la construction répétée d'objets est critique.

Code de la branche: https://github.com/marccollin/formatic/tree/annotation_processor

Migration d’une application Spring Boot 2.2 vers Spring Boot 4.0 : retour d’expérience concret

 En 2019, nous avons développé une application métier basée sur : Java 8 Spring Boot 2.2.7 Gradle 6.6.1 Thymeleaf Boots...