Spring Boot 3.4 : Toutes les nouveautés et améliorations

Découvrez les nouveautés de Spring Boot 3.4 : structured logging, virtual threads, graceful shutdown par défaut, RestClient amélioré et MockMvcTester.

Spring Boot 3.4 nouvelles fonctionnalités et améliorations

Spring Boot 3.4, sorti en novembre 2024, apporte des améliorations majeures pour la productivité des développeurs et les performances des applications. Cette version introduit le structured logging natif, active le graceful shutdown par défaut, et étend le support des virtual threads.

Prérequis

Spring Boot 3.4 nécessite Java 17 minimum et supporte Java 21 pour les virtual threads. Cette version utilise Spring Framework 6.2.

Structured Logging natif

Le structured logging représente une avancée majeure pour l'observabilité des applications. Au lieu de logs textuels difficiles à parser, Spring Boot 3.4 génère des logs au format JSON exploitables par des outils comme Elasticsearch, Grafana Loki ou Datadog.

Trois formats sont supportés nativement : Elastic Common Schema (ECS), Logstash et Graylog Extended Log Format (GELF).

properties
# application.properties
# Activer le structured logging en console
logging.structured.format.console=ecs

# Ou pour les fichiers de log
logging.structured.format.file=logstash

Cette configuration simple produit des logs JSON structurés automatiquement.

LoggingController.javajava
@RestController
@RequestMapping("/api/demo")
public class LoggingController {

    // Injection du logger via SLF4J
    private static final Logger logger = LoggerFactory.getLogger(LoggingController.class);

    @GetMapping("/action")
    public ResponseEntity<String> performAction(@RequestParam String userId) {
        // Le log sera automatiquement formaté en JSON structuré
        logger.info("Action performed by user: {}", userId);
        return ResponseEntity.ok("Action completed");
    }
}

Avec le format ECS activé, ce log devient un objet JSON contenant le timestamp, le niveau, le message, le nom de la classe, le thread et les métadonnées de l'application. Cette structure facilite les recherches et l'agrégation dans les plateformes de monitoring.

Graceful Shutdown activé par défaut

Changement important : le graceful shutdown est maintenant activé par défaut. Les requêtes HTTP en cours sont traitées avant l'arrêt du serveur, évitant les erreurs 502 lors des déploiements.

properties
# application.properties
# Le graceful shutdown est maintenant ON par défaut
# Pour restaurer l'ancien comportement (arrêt immédiat) :
server.shutdown=immediate

# Configurer le délai maximum d'attente (30s par défaut)
spring.lifecycle.timeout-per-shutdown-phase=45s

Ce comportement s'applique à tous les serveurs embarqués : Tomcat, Jetty, Undertow et Reactor Netty.

LifecycleConfig.javajava
@Configuration
public class LifecycleConfig {

    private static final Logger logger = LoggerFactory.getLogger(LifecycleConfig.class);

    @Bean
    public ApplicationListener<ContextClosedEvent> gracefulShutdownListener() {
        // Ce listener s'exécute au début du shutdown
        return event -> {
            logger.info("Graceful shutdown initiated - completing in-flight requests");
            // Logique personnalisée : fermer des connexions, sauvegarder l'état, etc.
        };
    }
}

Cette amélioration rend les déploiements zero-downtime plus simples à implémenter, sans configuration supplémentaire dans la plupart des cas.

Virtual Threads étendus

Spring Boot 3.4 étend le support des virtual threads (Java 21+) à davantage de composants. Le OtlpMeterRegistry et le serveur Undertow utilisent maintenant les virtual threads lorsqu'ils sont activés.

properties
# application.properties
# Activer les virtual threads globalement
spring.threads.virtual.enabled=true

Cette simple propriété transforme le modèle de threading de l'application. Chaque requête HTTP obtient son propre virtual thread, permettant de gérer des milliers de connexions simultanées sans épuiser le pool de threads.

AsyncService.javajava
@Service
public class AsyncService {

    private static final Logger logger = LoggerFactory.getLogger(AsyncService.class);

    // Avec virtual threads activés, chaque appel bloquant
    // ne consomme plus de thread OS
    public String fetchExternalData() {
        logger.info("Executing on thread: {}", Thread.currentThread());

        // Appel HTTP bloquant - avec virtual threads,
        // le thread OS est libéré pendant l'attente I/O
        return restClient.get()
            .uri("https://api.external.com/data")
            .retrieve()
            .body(String.class);
    }
}

Les virtual threads brillent pour les applications I/O-bound : appels HTTP, requêtes base de données, opérations fichiers. Le thread OS est libéré pendant l'attente, puis réassigné à la reprise.

Compatibilité

Les virtual threads nécessitent Java 21 minimum. Sur Java 17, cette propriété est ignorée et le comportement classique s'applique.

RestClient et RestTemplate améliorés

Spring Boot 3.4 normalise la configuration des clients HTTP. La sélection du HttpRequestFactory suit maintenant une priorité claire basée sur le classpath.

HttpClientConfig.javajava
@Configuration
public class HttpClientConfig {

    @Bean
    public RestClient restClient(RestClient.Builder builder) {
        // Spring Boot choisit automatiquement l'implémentation :
        // 1. Apache HTTP Components (si présent)
        // 2. Jetty Client
        // 3. Reactor Netty
        // 4. JDK HttpClient (Java 11+)
        // 5. SimpleClientHttpRequestFactory (fallback)
        return builder
            .baseUrl("https://api.example.com")
            .defaultHeader("Accept", "application/json")
            .build();
    }
}

La configuration du comportement de redirection est également simplifiée.

properties
# application.properties
# Forcer une implémentation spécifique
spring.http.client.factory=jdk

# Configurer le comportement des redirections
spring.http.client.redirects=dont-follow

Pour un contrôle plus fin, le nouveau ClientHttpRequestFactoryBuilder permet une configuration programmatique complète.

CustomHttpClientConfig.javajava
@Configuration
public class CustomHttpClientConfig {

    @Bean
    public RestClient customRestClient(ClientHttpRequestFactoryBuilder factoryBuilder) {
        // Configuration avancée du factory
        ClientHttpRequestFactory factory = factoryBuilder
            .httpComponents()
            .connectTimeout(Duration.ofSeconds(5))
            .readTimeout(Duration.ofSeconds(30))
            .build();

        return RestClient.builder()
            .requestFactory(factory)
            .baseUrl("https://api.example.com")
            .build();
    }
}

Cette approche offre une flexibilité maximale tout en gardant une API cohérente, quelle que soit l'implémentation HTTP sous-jacente.

Prêt à réussir tes entretiens Spring Boot ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

MockMvcTester pour des tests fluides

Spring Boot 3.4 introduit MockMvcTester, une alternative à MockMvc basée sur l'API fluide d'AssertJ. Cette nouvelle approche rend les tests plus lisibles et expressifs.

UserControllerTest.javajava
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvcTester mockMvc; // Nouvelle classe !

    @Test
    void shouldReturnUserById() {
        // API fluide avec AssertJ
        mockMvc.get().uri("/api/users/{id}", 1)
            .assertThat()
            .hasStatusOk()
            .hasContentType(MediaType.APPLICATION_JSON)
            .bodyJson()
            .extractingPath("$.name")
            .isEqualTo("John Doe");
    }

    @Test
    void shouldReturn404ForUnknownUser() {
        mockMvc.get().uri("/api/users/{id}", 999)
            .assertThat()
            .hasStatus(HttpStatus.NOT_FOUND)
            .bodyJson()
            .extractingPath("$.error")
            .isEqualTo("User not found");
    }
}

Comparé à l'ancienne approche avec MockMvc, les assertions sont plus concises et le chaînage plus naturel.

ComparisonTest.javajava
@WebMvcTest(UserController.class)
class ComparisonTest {

    @Autowired
    private MockMvcTester mockMvcTester;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void withMockMvcTester() {
        // Nouvelle approche : fluide et concise
        mockMvcTester.post().uri("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\": \"Jane\"}")
            .assertThat()
            .hasStatus(HttpStatus.CREATED)
            .hasHeader("Location", "/api/users/2");
    }

    @Test
    void withClassicMockMvc() throws Exception {
        // Ancienne approche : plus verbeuse
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\": \"Jane\"}"))
            .andExpect(status().isCreated())
            .andExpect(header().string("Location", "/api/users/2"));
    }
}

MockMvcTester est auto-configuré lorsque AssertJ est présent dans le classpath (inclus par défaut avec spring-boot-starter-test).

Docker Compose et Testcontainers enrichis

Le support Docker Compose gagne en flexibilité avec la possibilité d'utiliser plusieurs fichiers de configuration et de passer des arguments personnalisés.

properties
# application.properties
# Utiliser plusieurs fichiers Docker Compose
spring.docker.compose.file=compose.yaml,compose-dev.yaml

# Passer des arguments au démarrage
spring.docker.compose.start.arguments=--scale redis=2

# Arguments à l'arrêt
spring.docker.compose.stop.arguments=--timeout 60

De nouveaux services sont automatiquement détectés et configurés.

yaml
# compose.yaml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"

  redis-stack:
    image: redis/redis-stack:latest
    ports:
      - "6379:6379"
      - "8001:8001" # RedisInsight UI

  grafana-lgtm:
    image: grafana/otel-lgtm:latest
    ports:
      - "3000:3000"  # Grafana
      - "4317:4317"  # OTLP gRPC

Spring Boot 3.4 détecte automatiquement ces services et configure les propriétés de connexion correspondantes.

Pour les tests avec Testcontainers, de nouveaux conteneurs sont supportés.

IntegrationTestConfig.javajava
@TestConfiguration(proxyBeanMethods = false)
public class IntegrationTestConfig {

    @Bean
    @ServiceConnection
    public PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>("postgres:16");
    }

    @Bean
    @ServiceConnection
    public RedisStackContainer redisStackContainer() {
        // Nouveau support Redis Stack
        return new RedisStackContainer("redis/redis-stack:latest");
    }

    @Bean
    @ServiceConnection
    public LgtmStackContainer observabilityContainer() {
        // Nouveau support Grafana LGTM (Loki, Grafana, Tempo, Mimir)
        return new LgtmStackContainer("grafana/otel-lgtm:latest");
    }
}

L'annotation @ServiceConnection configure automatiquement les propriétés de connexion, éliminant le besoin de @DynamicPropertySource dans la plupart des cas.

Actuator SSL et observabilité

Le nouveau endpoint /actuator/info expose maintenant des informations sur les certificats SSL configurés : dates de validité, émetteur et sujet.

properties
# application.properties
# Activer les informations SSL dans l'actuator
management.info.ssl.enabled=true

# Configurer un seuil d'alerte pour les certificats expirant bientôt
management.health.ssl.certificate-validity-warning-threshold=30d

Cette fonctionnalité permet de monitorer l'expiration des certificats directement via l'actuator, facilitant l'automatisation des renouvellements.

SslMonitoringConfig.javajava
@Configuration
public class SslMonitoringConfig {

    @Bean
    public HealthIndicator sslCertificateHealth(SslInfo sslInfo) {
        return () -> {
            // Vérification personnalisée de la validité des certificats
            boolean allValid = sslInfo.getBundles().values().stream()
                .flatMap(bundle -> bundle.getCertificates().stream())
                .allMatch(cert -> cert.getValidityEnds().isAfter(Instant.now()));

            return allValid
                ? Health.up().build()
                : Health.down().withDetail("reason", "Certificate expiring soon").build();
        };
    }
}

Pour l'observabilité, le transport OTLP supporte maintenant gRPC en plus de HTTP.

properties
# application.properties
# Utiliser gRPC pour OTLP (traces et métriques)
management.otlp.tracing.transport=grpc
management.otlp.tracing.endpoint=http://localhost:4317

# Nouveau : grouper les applications
spring.application.group=payment-services

Le groupe d'application (spring.application.group) permet de regrouper logiquement plusieurs services dans les dashboards d'observabilité.

Images OCI plus légères

Le builder par défaut pour les images OCI change de paketobuildpacks/builder-jammy-base vers paketobuildpacks/builder-jammy-java-tiny, produisant des images significativement plus petites.

build.gradlegroovy
tasks.named("bootBuildImage") {
    // Support ARM natif (nouveau)
    imagePlatform = "linux/arm64"

    // Nouveau flag de sécurité
    trustBuilder = false

    // Configuration de l'image
    imageName = "myregistry.com/myapp:${version}"
}

Le nouveau paramètre imagePlatform facilite les builds cross-platform pour ARM et x64.

xml
<!-- pom.xml -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <!-- Support ARM natif -->
            <platform>linux/arm64</platform>
            <!-- Builder optimisé pour images minimales -->
            <builder>paketobuildpacks/builder-jammy-java-tiny</builder>
        </image>
    </configuration>
</plugin>

Ces images optimisées démarrent plus rapidement et consomment moins de ressources, particulièrement avantageux pour les déploiements Kubernetes.

Migration

Le changement de builder peut affecter les applications dépendant de certains outils système. Testez la nouvelle image avant de déployer en production.

Changements de validation des beans

Spring Boot 3.4 aligne le comportement de validation sur la spécification Bean Validation. La validation ne cascade plus automatiquement vers les propriétés imbriquées.

ConfigProperties.javajava
@ConfigurationProperties(prefix = "app")
@Validated
public class AppConfig {

    @NotBlank
    private String name;

    // IMPORTANT : @Valid requis pour cascader la validation
    @Valid
    private DatabaseConfig database;

    // Sans @Valid, les contraintes de ServerConfig ne seront PAS vérifiées
    private ServerConfig server;

    // Getters et setters
}

public class DatabaseConfig {
    @NotBlank
    private String url;

    @Min(1)
    private int poolSize;

    // Getters et setters
}

public class ServerConfig {
    @NotNull // Cette contrainte ne sera PAS vérifiée sans @Valid sur le parent
    private Integer port;

    // Getters et setters
}

Ce changement peut casser des applications existantes. Auditez les classes @ConfigurationProperties et ajoutez @Valid là où la validation doit cascader.

Dépréciation de @MockBean et @SpyBean

Les annotations @MockBean et @SpyBean de Spring Boot sont dépréciées au profit des nouvelles annotations Mockito.

UserServiceTest.java (nouvelle approche)java
@SpringBootTest
class UserServiceTest {

    // Nouveau : annotations Mockito natives
    @MockitoBean
    private UserRepository userRepository;

    @MockitoSpyBean
    private EmailService emailService;

    @Autowired
    private UserService userService;

    @Test
    void shouldCreateUser() {
        // Configuration du mock
        when(userRepository.save(any())).thenReturn(new User(1L, "test@example.com"));

        userService.createUser("test@example.com");

        // Vérification du spy
        verify(emailService).sendWelcomeEmail("test@example.com");
    }
}

Les anciennes annotations fonctionnent encore mais affichent un avertissement de dépréciation. Planifiez la migration vers @MockitoBean et @MockitoSpyBean.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Conclusion

Spring Boot 3.4 apporte des améliorations substantielles pour la productivité et l'observabilité :

Checklist de migration :

  • ✅ Activer le structured logging pour améliorer l'observabilité
  • ✅ Vérifier le comportement du graceful shutdown (maintenant activé par défaut)
  • ✅ Considérer les virtual threads pour les applications I/O-bound (Java 21+)
  • ✅ Migrer vers MockMvcTester pour des tests plus lisibles
  • ✅ Ajouter @Valid sur les propriétés imbriquées des @ConfigurationProperties
  • ✅ Remplacer @MockBean/@SpyBean par @MockitoBean/@MockitoSpyBean
  • ✅ Tester les nouvelles images OCI avant déploiement en production

Spring Boot 3.4 consolide la position du framework comme référence pour le développement Java moderne, avec une attention particulière portée aux standards d'observabilité et aux performances.

Sources :

Tags

#spring boot
#java
#spring boot 3.4
#spring framework
#backend

Partager

Articles similaires