Catégories
Informatique & Dev

Migration Java 21 vers Java 25 sur une application Spring Boot réelle : du code à la mise en production

Migration Java 21 vers Java 25

Migrer une application Java ne consiste pas à changer une simple propriété Maven et espérer que tout continue à fonctionner.

Sur un projet réel, une montée de version impacte bien plus que le code applicatif : build Maven, plugins, dépendances, tests, analyse qualité, CI/CD, images Docker et comportement d’exécution en conteneur.

Dans cet article, je documente une migration réelle de Java 21 vers Java 25 sur mon projet fullstack open source publiquement accessible.

Java 25 étant une version LTS, l’objectif n’est pas ici de recommander une migration systématique, mais de documenter concrètement une migration technique complète sur un projet moderne réellement déployé.

Le frontend Angular existe bien dans le projet, mais il est volontairement hors périmètre ici. L’objectif est de se concentrer uniquement sur la migration du backend Spring Boot et de toute sa chaîne de livraison.

Le backend concerné repose actuellement sur :

  • Spring Boot 3.5.x
  • Java 21
  • Maven
  • PostgreSQL
  • Flyway
  • authentification JWT via cookies HTTPOnly sécurisés (+ protections CSRF)
  • tests backend (unitaires / intégration)
  • tests API de bout en bout avec Bruno
  • SonarCloud
  • GitHub Actions
  • Construction d’images Docker multi-étapes
  • déploiement automatisé sur VPS conteneurisé (Docker)

Le but n’est pas de produire un tutoriel générique.

L’objectif est de montrer une démarche pragmatique de migration technique sur une application réellement livrée, avec une exigence simple : faire évoluer la pile technique sans compromettre la chaîne de livraison.

Les prochaines évolutions — Spring Boot 4 côté backend et Angular 21 côté frontend — seront traitées séparément dans d’autres articles afin de conserver une migration lisible, testable et isolée.

Le projet

Le code source et l’application déployée sont accessibles publiquement pour suivre ou reproduire les différentes étapes.

1. État des lieux : un projet stable avant toute migration

Avant de modifier quoi que ce soit, un prérequis simple s’impose : partir d’un état stable.

Une migration technique n’est pas le bon moment pour découvrir des problèmes existants, des tests instables ou une chaîne d’intégration déjà fragilisée.

Sinon, impossible de savoir si un incident provient réellement de la montée de version Java… ou d’un problème préexistant simplement révélé au mauvais moment.

Dans ce projet, la chaîne de construction, de validation et de déploiement est déjà pleinement opérationnelle avant le premier changement.

L’état initial avant migration est le suivant :

2. Définir une stratégie de migration avant de toucher au code

La tentation la plus simple aurait été de modifier immédiatement la version Java dans le projet et d’observer ce qui casse.

C’est précisément ce que je voulais éviter.

Sur une migration technique réelle, l’objectif n’est pas seulement de faire compiler le code, mais de comprendre l’ensemble des impacts potentiels avant le premier changement.

Un audit préalable du projet a permis d’identifier les zones réellement concernées par cette migration.

Bonne nouvelle : le code applicatif lui-même ne présente pas de dépendance directe à Java 21.

Les principaux points d’attention se situent ailleurs :

– configuration Maven
– plugins de build
– couverture de code avec JaCoCo
– formatage Java via Spotless / google-java-format
– images Docker de build et d’exécution
– workflows GitHub Actions
– documentation technique

Autrement dit : exactement le type d’éléments qui cassent souvent silencieusement une migration si l’on se limite à changer <java.version>.

Le périmètre a donc été volontairement limité à la seule migration Java 21 vers Java 25.

Les autres évolutions techniques prévues — notamment Spring Boot 4 côté backend et Angular 21 côté frontend — seront traitées séparément.

Une précision sur le périmètre applicatif

Un point mérite toutefois d’être précisé.

Le backend utilisé comme support de cette migration reste volontairement relativement simple sur le plan fonctionnel. Entre Java 21 et Java 25, le risque de rupture directe dans le code métier était donc objectivement limité.

Ce n’est pas un monolithe ancien avec plusieurs années d’accumulation technique ou des dépendances historiques difficiles à faire évoluer.

Mais ce n’est pas non plus l’objet principal de cette démarche.

L’intérêt de cette migration réside surtout dans la validation de tout l’écosystème technique autour de l’application : build, tests, couverture, formatage, intégration continue, conteneurisation et déploiement automatisé.

Autrement dit : même lorsque le code applicatif évolue peu, une migration Java reste un exercice intéressant dès lors que l’application s’inscrit dans une vraie chaîne de livraison.

Cette approche permet de conserver un diagnostic clair : si un problème apparaît, la cause probable reste immédiatement identifiable.

2.1 Mettre à niveau JaCoCo avant le changement de JDK

L’un des premiers points identifiés lors de l’audit concernait JaCoCo, utilisé pour la couverture de code dans la chaîne de validation.

La version présente dans le projet était fonctionnelle avec Java 21, mais ne supportait pas officiellement Java 25.

Ce point est important car JaCoCo ne se contente pas d’analyser du code compilé : il s’appuie sur l’instrumentation du bytecode pendant l’exécution des tests.

Autrement dit, une incompatibilité à ce niveau peut faire échouer la validation bien avant même d’atteindre le code applicatif.

La mise à niveau vers une version compatible Java 25 (0.8.14) a donc été réalisée avant toute modification de la version du JDK du projet.

C’est typiquement le genre de dépendance d’outillage que l’on oublie facilement lorsqu’on réduit une migration Java à un simple changement de version dans le fichier Maven.

2.2 Rendre explicite la version de google-java-format

Le formatage automatique du code Java est vérifié dans la chaîne de validation via Spotless.

À première vue, rien de problématique : le plugin était déjà en place et fonctionnel.

En regardant de plus près, un point méritait toutefois d’être clarifié : la version de `google-java-format` n’était pas explicitement définie.

Or cet outil n’est pas anodin dans le cadre d’une migration Java. Il s’appuie sur les API du compilateur Java (`javac`) pour analyser et reformater le code source, ce qui le rend potentiellement sensible aux évolutions du JDK.

L’objectif n’était donc pas seulement de mettre Spotless à jour, mais aussi de rendre explicite la version réellement utilisée par google-java-format (1.34.1).

Cela permet :

– de mieux maîtriser le comportement du formatage dans le temps ;
– d’éviter une dépendance implicite difficile à diagnostiquer en cas de problème ;
– de sécuriser la compatibilité avec Java 25.

Ce type d’ajustement n’impacte pas le code métier, mais participe directement à la fiabilité globale de la migration.

2.3 Un avertissement existant corrigé au passage

La phase de préparation a également permis de corriger un point qui n’était pas directement lié à Java 25, mais qui méritait d’être traité.

Lors de l’exécution des tests, Mockito affichait déjà sous Java 21 l’avertissement suivant :

Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK.

En clair : Mockito s’attachait dynamiquement à la JVM pour activer certaines fonctionnalités de mock, mais ce mécanisme est progressivement restreint par les versions récentes du JDK.

Ce n’était donc pas une régression liée à la migration, mais une dette technique déjà présente.

Le passage vers Java 25 était une bonne occasion de corriger proprement ce point en appliquant la configuration désormais recommandée par Mockito, avec déclaration explicite de l’agent Java dans le processus de test.

3. Basculer le projet vers Java 25

Une fois l’outillage sécurisé, le passage effectif à Java 25 devient beaucoup plus simple.

La première étape consiste à installer localement un JDK 25 et à vérifier que l’environnement utilise bien cette version.

Les contrôles de base :

java -version
javac -version
./mvnw -version

Le point important n’est pas seulement la présence du JDK, mais la cohérence complète de l’environnement.

Aide-mémoire Ubuntu

Sous Ubuntu, plusieurs approches sont possibles pour installer un nouveau JDK.
Vérifier les versions disponibles :

apt search openjdk

Installer Java 25 :

sudo apt install openjdk-25-jdk

Vérifier la version active :

java -version

Si plusieurs JDK sont installés :

sudo update-alternatives --config java
sudo update-alternatives --config javac


Vérifier enfin que Maven utilise bien le bon runtime :

./mvnw -version

Un pom.xml configuré pour Java 25 avec un Maven exécuté sur un JDK 21 provoquera immédiatement une erreur du type :

release version 25 not supported

Une fois l’environnement aligné, la modification côté Maven est volontairement minimale :

<java.version>25</java.version>

Dans ce projet, cette propriété pilote également la configuration effective du compilateur Maven via le parent Spring Boot.

Cela évite de maintenir séparément des propriétés `source`, `target` ou `release`, et permet une migration plus lisible.

4. Aligner la chaîne de livraison sur Java 25

Une migration locale validée ne suffit pas.

Dans ce projet, le backend n’est pas simplement exécuté sur une machine de développement : il est construit, testé, analysé et déployé automatiquement.

Une fois le passage à Java 25 validé localement via Maven, il restait donc à aligner toute la chaîne de livraison.

4.1 Mettre à jour le runtime Docker

Le backend est construit et exécuté via un Dockerfile multi-étapes.

Avant migration, les images utilisées reposaient sur Java 21, aussi bien pour la phase de build que pour l’image d’exécution.

Le changement était donc nécessaire à deux niveaux :

  • image de construction Maven
  • image runtime Eclipse Temurin

L’objectif n’était pas uniquement de compiler avec Java 25, mais bien de garantir que l’application s’exécute réellement dans ce nouvel environnement.

C’est un point important : une migration Java validée uniquement côté build local ne dit rien sur le comportement réel du conteneur livré.

4.2 Aligner les workflows GitHub Actions

Le projet s’appuie sur plusieurs workflows automatisés :

  • validation backend
  • analyse qualité SonarCloud
  • tests API de bout en bout avec Bruno
  • déploiement automatisé

Tous les workflows backend configurés avec Java 21 ont donc été alignés sur Java 25.

L’objectif était simple : garantir une cohérence complète entre environnement local, intégration continue et exécution réelle.

Sans cet alignement, on se retrouve rapidement avec une situation incohérente :

  • développement local en Java 25
  • intégration continue en Java 21
  • runtime conteneur encore différent

Ce type d’écart rend le diagnostic inutilement complexe.

4.3 Validation complète jusqu’au déploiement réel

Une fois Docker et les workflows mis à jour, la validation ne s’arrêtait pas à un simple build vert.

La chaîne complète devait être validée :

  • build backend
  • tests backend
  • couverture
  • analyse qualité
  • tests API Bruno
  • construction des images Docker
  • déploiement automatisé
  • démarrage réel du conteneur
  • vérification du point de santé applicatif

Le backend a bien été déployé avec Java 25 en runtime :

openjdk version "25.0.3"
OpenJDK Runtime Environment Temurin-25.0.3+9
OpenJDK 64-Bit Server VM Temurin-25.0.3+9

Le script de déploiement attend ensuite explicitement que l’application soit réellement disponible avant de considérer le déploiement comme réussi.

Ce dernier point est important : un conteneur démarré ne signifie pas automatiquement une application opérationnelle.

Conclusion

Au final, cette migration Java 21 vers Java 25 s’est révélée beaucoup moins être un sujet de compatibilité applicative qu’un exercice de validation de l’écosystème technique complet.

Le code métier du backend n’a nécessité aucune adaptation particulière, ce qui était cohérent avec le périmètre relativement simple du projet.

En revanche, plusieurs points d’outillage méritaient une attention réelle :

  • mise à niveau de JaCoCo pour la compatibilité Java 25 ;
  • explicitation et validation de la chaîne de formatage Java avec Spotless et google-java-format ;
  • correction d’un avertissement Mockito déjà présent sous Java 21 ;
  • alignement de l’environnement local, du build Maven, des workflows GitHub Actions et du runtime Docker.

C’est probablement le principal enseignement de cette migration.

Changer la version Java dans un pom.xml est souvent la partie la plus simple.

La vraie question est ailleurs : est-ce que toute la chaîne continue à fonctionner correctement, jusqu’au déploiement réel ?

Dans ce cas précis, la réponse est oui.

Le backend compile, les tests passent, la couverture reste opérationnelle, les validations automatisées restent au vert, les tests API continuent de passer, et l’application tourne effectivement sous Java 25 en production conteneurisée.

Les prochaines étapes seront traitées séparément, avec un même principe de migration isolée et maîtrisée :

  • Spring Boot 4 côté backend
  • Angular 21 côté frontend

Même sur un projet applicativement simple, ce type de migration reste un excellent exercice pour maintenir une chaîne technique saine, actuelle et réellement vérifiée.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *