Spring Boot 4 : breaking changes à connaître avant de migrer

Spring Boot 4 : breaking changes à connaître avant de migrer

Vous envisagez de migrer vers Spring Boot 4 ? Avant de lancer ./mvnw spring-boot:upgrade, prenez le temps de lire ce qui suit. Cette version majeure introduit des changements incompatibles qui peuvent casser votre application si vous n'êtes pas préparé.

Voici la liste complète des breaking changes à connaître :

  • Java 21 minimum requis
  • Passage à Jakarta EE 11
  • Hibernate 7 et changements JPA
  • Nouvelle configuration de sécurité
  • Suppression des propriétés dépréciées
  • Changements dans les starters
  • Modifications des métriques et observabilité
  • Évolutions du support cloud natif

Pour une analyse complète sur l'opportunité de migrer, consultez notre article pilier : Spring Boot 4 : faut-il migrer maintenant ?

Java 21 : le minimum requis

Ce qui change

Spring Boot 4 abandonne le support de Java 17. La version minimale est désormais Java 21 LTS.

<!-- pom.xml - Avant (Spring Boot 3.x) -->
<properties>
    <java.version>17</java.version>
</properties>

<!-- pom.xml - Après (Spring Boot 4.x) -->
<properties>
    <java.version>21</java.version>
</properties>

Impact sur votre projet

Situation Action requise
Déjà en Java 21 Aucune action
Java 17 Mise à jour JDK + tests de régression
Java 11 ou moins Migration majeure (2 versions)

Fonctionnalités Java 21 à exploiter

Spring Boot 4 tire parti des nouvelles fonctionnalités :

// Virtual Threads (Project Loom) - natif Spring Boot 4
@Bean
public TomcatProtocolHandlerCustomizer<?> virtualThreadsCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    };
}

// Pattern Matching (Java 21)
public String processPayment(Payment payment) {
    return switch (payment) {
        case CreditCard cc -> processCreditCard(cc);
        case BankTransfer bt -> processBankTransfer(bt);
        case Crypto crypto -> processCrypto(crypto);
    };
}

// Record Patterns
if (response instanceof ApiResponse(var data, var status)
    && status == 200) {
    return data;
}

Checklist Java 21

  • ☐ Vérifier la compatibilité de toutes les dépendances avec Java 21
  • ☐ Mettre à jour le JDK sur tous les environnements (dev, CI, prod)
  • ☐ Tester les performances (GC, startup time)
  • ☐ Mettre à jour les images Docker (eclipse-temurin:21-jre)

Jakarta EE 11 : le grand saut

Ce qui change

Spring Boot 4 passe de Jakarta EE 10 à Jakarta EE 11. Les impacts principaux :

// Changements de packages - déjà effectués en Spring Boot 3
// javax.* → jakarta.* (rappel)

// NOUVEAU en Jakarta EE 11 : APIs supprimées
// jakarta.activation → standalone
// jakarta.xml.bind → standalone (JAXB)

APIs supprimées du namespace Jakarta

API Statut Alternative
JAXB (XML Binding) Retiré de Jakarta EE org.glassfish.jaxb:jaxb-runtime
JAX-WS (SOAP) Retiré com.sun.xml.ws:jaxws-rt
Jakarta Activation Standalone jakarta.activation:jakarta.activation-api
CommonJ Supprimé java.util.concurrent

Migration des dépendances XML

<!-- Si vous utilisez JAXB (XML) -->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>4.0.4</version>
</dependency>

<!-- Si vous utilisez SOAP (rare en 2025) -->
<dependency>
    <groupId>com.sun.xml.ws</groupId>
    <artifactId>jaxws-rt</artifactId>
    <version>4.0.2</version>
</dependency>

Servlet 6.1

// Nouvelle API Servlet 6.1
// Changement : HttpServletRequest.getRequestURL()
// Retourne maintenant StringBuilder (non StringBuffer)

// Avant
StringBuffer url = request.getRequestURL();

// Après (compatible)
String url = request.getRequestURL().toString();

Hibernate 7 et les changements JPA

Ce qui change

Spring Boot 4 embarque Hibernate 7 avec des breaking changes significatifs.

Suppression des APIs dépréciées

// SUPPRIMÉ : Criteria API legacy
// Avant (Hibernate 5/6)
Criteria criteria = session.createCriteria(User.class);

// Après (Hibernate 7) - Utiliser JPA Criteria
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);

Changements de types

// Type UUID natif
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;  // Support natif amélioré
}

// Nouveau : @SoftDelete natif
@Entity
@SoftDelete  // Hibernate 7
public class Document {
    // deleted_at géré automatiquement
}

Configuration Hibernate modifiée

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 50

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 50
        query:
          mutation_strategy: inline  # Nouveau

Lazy Loading par défaut

// ATTENTION : Changement de comportement par défaut
// Hibernate 7 : @ManyToOne est LAZY par défaut (était EAGER)

@Entity
public class Order {
    @ManyToOne  // Maintenant LAZY par défaut
    private Customer customer;

    // Si vous vouliez EAGER, expliciter :
    @ManyToOne(fetch = FetchType.EAGER)
    private Customer customerEager;
}

Sécurité : nouvelle configuration obligatoire

Suppression de la configuration dépréciée

// SUPPRIMÉ en Spring Boot 4
// WebSecurityConfigurerAdapter n'existe plus depuis SB 3
// Mais certains patterns dépréciés sont maintenant ERREUR

// AVANT (toléré en SB 3, erreur en SB 4)
http.authorizeRequests()
    .antMatchers("/api/**").authenticated();

// APRÈS (obligatoire)
http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/api/**").authenticated()
);

Nouvelles valeurs par défaut

spring:
  security:
    # NOUVEAU : CSRF activé par défaut même pour APIs REST
    # Désactiver explicitement si API stateless
    csrf:
      enabled: true

    # NOUVEAU : Headers de sécurité renforcés
    headers:
      content-security-policy: "default-src 'self'"
      permissions-policy: "geolocation=(), microphone=()"

Configuration CORS stricte

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            // NOUVEAU : CORS doit être configuré explicitement
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            // CSRF désactivé pour API REST stateless
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/**").authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://app.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("*"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

Propriétés supprimées et renommées

Propriétés supprimées

logging.pattern.file: "%d{yyyy-MM-dd} - %msg%n"  # Utiliser logging.pattern.dateformat

spring.datasource.initialization-mode: always  # Utiliser spring.sql.init.mode

management.endpoints.jmx.exposure.include: "*"  # Renommé

Propriétés renommées

Ancienne propriété Nouvelle propriété
spring.datasource.initialization-mode spring.sql.init.mode
spring.jpa.hibernate.use-new-id-generator-mappings Supprimée (toujours true)
server.servlet.encoding.enabled server.servlet.encoding.enabled (inchangé mais vérifié)
management.metrics.tags.* management.observations.key-values.*

Script de détection automatique

grep -r "spring.datasource.initialization-mode" src/
grep -r "use-new-id-generator-mappings" src/
grep -r "management.metrics.tags" src/

Changements dans les starters

Starters supprimés

<!-- ❌ SUPPRIMÉS - Ne plus utiliser -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!-- Remplacé par : configuration manuelle ou Tomcat/Undertow -->

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Log4j2 reste supporté mais configuration différente -->

Starters modifiés

<!-- spring-boot-starter-web -->
<!-- Tomcat 11 inclus par défaut -->
<!-- Virtual Threads activables via propriété -->

<!-- spring-boot-starter-data-jpa -->
<!-- Hibernate 7 par défaut -->
<!-- HikariCP 6 inclus -->

<!-- spring-boot-starter-security -->
<!-- Spring Security 7 -->
<!-- OAuth2 Authorization Server séparé -->

Nouveau starter observabilité

<!-- NOUVEAU : Starter unifié pour observabilité -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-observability</artifactId>
</dependency>
<!-- Inclut : Micrometer, Tracing, Metrics, Logging structuré -->

Métriques et observabilité

Migration Micrometer 2.0

// CHANGEMENT : API Micrometer modifiée

// AVANT (Micrometer 1.x)
Metrics.counter("api.requests", "endpoint", "/users").increment();

// APRÈS (Micrometer 2.x)
Metrics.counter("api.requests", Tags.of("endpoint", "/users")).increment();

// NOUVEAU : Observations API (recommandé)
@Observed(name = "user.service", contextualName = "get-user")
public User getUser(Long id) {
    return userRepository.findById(id).orElseThrow();
}

Nouvelles propriétés

management:
  observations:
    key-values:
      application: my-app
      environment: production

  tracing:
    sampling:
      probability: 1.0  # 100% en dev, réduire en prod

  otlp:
    tracing:
      endpoint: http://jaeger:4318/v1/traces
    metrics:
      endpoint: http://prometheus:4318/v1/metrics

Logging structuré par défaut

logging:
  structured:
    format:
      console: ecs  # Elastic Common Schema
      file: json

  # Corrélation de traces automatique
  pattern:
    correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

Cloud natif : GraalVM et containers

Native Image amélioré

<!-- pom.xml - Configuration native simplifiée -->
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <!-- Version gérée par Spring Boot 4 -->
    <configuration>
        <!-- NOUVEAU : Options simplifiées -->
        <buildArgs>
            <buildArg>--initialize-at-build-time=org.slf4j</buildArg>
        </buildArgs>
    </configuration>
</plugin>

Buildpacks mis à jour

./mvnw spring-boot:build-image \
  -Dspring-boot.build-image.imageName=myapp:latest

CDS (Class Data Sharing)

FROM eclipse-temurin:21-jre as builder
WORKDIR /app
COPY target/*.jar app.jar
RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder /app/extracted/dependencies/ ./
COPY --from=builder /app/extracted/spring-boot-loader/ ./
COPY --from=builder /app/extracted/snapshot-dependencies/ ./
COPY --from=builder /app/extracted/application/ ./

RUN java -XX:ArchiveClassesAtExit=app-cds.jsa -Dspring.context.exit=onRefresh -jar app.jar || true
ENTRYPOINT ["java", "-XX:SharedArchiveFile=app-cds.jsa", "-jar", "app.jar"]

Checklist de migration complète

Avant de migrer vers Spring Boot 4, validez chaque point :

Prérequis

  • ☐ Java 21 installé sur tous les environnements
  • ☐ Toutes les dépendances compatibles Java 21
  • ☐ Tests de régression à jour
  • ☐ Backup de la configuration actuelle

Code

  • ☐ Remplacer authorizeRequests() par authorizeHttpRequests()
  • ☐ Vérifier les @ManyToOne (maintenant LAZY par défaut)
  • ☐ Migrer les Criteria Hibernate legacy vers JPA Criteria
  • ☐ Mettre à jour les appels Micrometer

Configuration

  • ☐ Renommer les propriétés dépréciées
  • ☐ Configurer CORS explicitement
  • ☐ Vérifier les paramètres de sécurité
  • ☐ Mettre à jour les profils de logging

Infrastructure

  • ☐ Mettre à jour les images Docker (Java 21)
  • ☐ Tester le native image si utilisé
  • ☐ Valider les dashboards de monitoring
  • ☐ Mettre à jour les pipelines CI/CD

Tests

  • ☐ Exécuter tous les tests unitaires
  • ☐ Exécuter les tests d'intégration
  • ☐ Tests de performance (startup, mémoire)
  • ☐ Tests de charge en environnement de staging

Ressources officielles

Pour aller plus loin :


Cette liste de breaking changes vous permet d'anticiper les problèmes avant qu'ils ne surviennent en production. Pour une analyse plus stratégique sur l'opportunité de migrer maintenant ou d'attendre, consultez Spring Boot 4 : faut-il migrer maintenant ?