Spring Boot 3.4: Todas las novedades explicadas

Spring Boot 3.4 trae logging estructurado nativo, virtual threads extendidos, graceful shutdown por defecto y MockMvcTester. Guía completa de las nuevas funcionalidades.

Nuevas funcionalidades y mejoras de Spring Boot 3.4

Spring Boot 3.4, lanzado en noviembre de 2024, aporta mejoras significativas en productividad y rendimiento de aplicaciones. Esta versión introduce logging estructurado nativo, habilita graceful shutdown por defecto y amplía el soporte de virtual threads en todo el framework.

Requisitos previos

Spring Boot 3.4 requiere Java 17 como mínimo y soporta Java 21 para virtual threads. Esta versión utiliza Spring Framework 6.2.

Logging estructurado nativo

El logging estructurado representa un avance importante para la observabilidad de aplicaciones. En lugar de logs basados en texto difíciles de analizar, Spring Boot 3.4 genera logs en formato JSON consumibles por herramientas como Elasticsearch, Grafana Loki o Datadog.

Tres formatos son soportados de forma nativa: Elastic Common Schema (ECS), Logstash y Graylog Extended Log Format (GELF).

properties
# application.properties
# Enable structured logging in console
logging.structured.format.console=ecs

# Or for log files
logging.structured.format.file=logstash

Esta simple configuración produce logs JSON estructurados de forma automática.

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

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

    @GetMapping("/action")
    public ResponseEntity<String> performAction(@RequestParam String userId) {
        // Log will be automatically formatted as structured JSON
        logger.info("Action performed by user: {}", userId);
        return ResponseEntity.ok("Action completed");
    }
}

Con el formato ECS habilitado, este log se convierte en un objeto JSON que contiene timestamp, nivel, mensaje, nombre de clase, hilo y metadatos de la aplicación. Esta estructura simplifica la búsqueda y agregación en plataformas de monitoreo.

Graceful Shutdown habilitado por defecto

Cambio importante: el graceful shutdown está ahora habilitado por defecto. Las peticiones HTTP en curso se procesan antes del cierre del servidor, evitando errores 502 durante los despliegues.

properties
# application.properties
# Graceful shutdown is now ON by default
# To restore previous behavior (immediate shutdown):
server.shutdown=immediate

# Configure maximum wait timeout (30s default)
spring.lifecycle.timeout-per-shutdown-phase=45s

Este comportamiento aplica a todos los servidores embebidos: Tomcat, Jetty, Undertow y Reactor Netty.

LifecycleConfig.javajava
@Configuration
public class LifecycleConfig {

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

    @Bean
    public ApplicationListener<ContextClosedEvent> gracefulShutdownListener() {
        // This listener executes at shutdown start
        return event -> {
            logger.info("Graceful shutdown initiated - completing in-flight requests");
            // Custom logic: close connections, save state, etc.
        };
    }
}

Esta mejora simplifica la implementación de despliegues sin tiempo de inactividad, sin necesidad de configuración adicional en la mayoría de los casos.

Soporte extendido de Virtual Threads

Spring Boot 3.4 extiende el soporte de virtual threads (Java 21+) a más componentes. OtlpMeterRegistry y el servidor Undertow ahora utilizan virtual threads cuando están habilitados.

properties
# application.properties
# Enable virtual threads globally
spring.threads.virtual.enabled=true

Esta única propiedad transforma el modelo de hilos de la aplicación. Cada petición HTTP obtiene su propio virtual thread, permitiendo miles de conexiones concurrentes sin agotar el pool de hilos.

AsyncService.javajava
@Service
public class AsyncService {

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

    // With virtual threads enabled, each blocking call
    // no longer consumes an OS thread
    public String fetchExternalData() {
        logger.info("Executing on thread: {}", Thread.currentThread());

        // Blocking HTTP call - with virtual threads,
        // the OS thread is released during I/O wait
        return restClient.get()
            .uri("https://api.external.com/data")
            .retrieve()
            .body(String.class);
    }
}

Los virtual threads destacan en aplicaciones con alta carga de I/O: llamadas HTTP, consultas a bases de datos, operaciones con archivos. El hilo del sistema operativo se libera durante el tiempo de espera y se reasigna al reanudar.

Compatibilidad

Los virtual threads requieren Java 21 como mínimo. En Java 17, esta propiedad se ignora y se aplica el comportamiento clásico.

RestClient y RestTemplate mejorados

Spring Boot 3.4 normaliza la configuración del cliente HTTP. La selección de HttpRequestFactory ahora sigue una precedencia clara basada en el classpath.

HttpClientConfig.javajava
@Configuration
public class HttpClientConfig {

    @Bean
    public RestClient restClient(RestClient.Builder builder) {
        // Spring Boot automatically chooses implementation:
        // 1. Apache HTTP Components (if present)
        // 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 configuración del comportamiento de redirecciones también se simplifica.

properties
# application.properties
# Force specific implementation
spring.http.client.factory=jdk

# Configure redirect behavior
spring.http.client.redirects=dont-follow

Para un control más detallado, el nuevo ClientHttpRequestFactoryBuilder permite una configuración programática completa.

CustomHttpClientConfig.javajava
@Configuration
public class CustomHttpClientConfig {

    @Bean
    public RestClient customRestClient(ClientHttpRequestFactoryBuilder factoryBuilder) {
        // Advanced factory configuration
        ClientHttpRequestFactory factory = factoryBuilder
            .httpComponents()
            .connectTimeout(Duration.ofSeconds(5))
            .readTimeout(Duration.ofSeconds(30))
            .build();

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

Este enfoque ofrece máxima flexibilidad manteniendo una API consistente independientemente de la implementación HTTP subyacente.

¿Listo para aprobar tus entrevistas de Spring Boot?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

MockMvcTester para testing fluido

Spring Boot 3.4 introduce MockMvcTester, una alternativa basada en AssertJ a MockMvc. Este nuevo enfoque hace que los tests sean más legibles y expresivos.

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

    @Autowired
    private MockMvcTester mockMvc; // New class!

    @Test
    void shouldReturnUserById() {
        // Fluent API with 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");
    }
}

Comparado con el enfoque anterior usando MockMvc, las aserciones son más concisas y el encadenamiento resulta más natural.

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

    @Autowired
    private MockMvcTester mockMvcTester;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void withMockMvcTester() {
        // New approach: fluent and 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 {
        // Old approach: more verbose
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\": \"Jane\"}"))
            .andExpect(status().isCreated())
            .andExpect(header().string("Location", "/api/users/2"));
    }
}

MockMvcTester se configura automáticamente cuando AssertJ está presente en el classpath (incluido por defecto con spring-boot-starter-test).

Docker Compose y Testcontainers mejorados

El soporte de Docker Compose gana flexibilidad con múltiples archivos de configuración y argumentos personalizados.

properties
# application.properties
# Use multiple Docker Compose files
spring.docker.compose.file=compose.yaml,compose-dev.yaml

# Pass arguments at startup
spring.docker.compose.start.arguments=--scale redis=2

# Arguments at shutdown
spring.docker.compose.stop.arguments=--timeout 60

Nuevos servicios se detectan y configuran automáticamente.

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 detecta automáticamente estos servicios y configura las propiedades de conexión correspondientes.

Para testing con Testcontainers, nuevos contenedores son soportados.

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

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

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

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

La anotación @ServiceConnection configura automáticamente las propiedades de conexión, eliminando la necesidad de @DynamicPropertySource en la mayoría de los casos.

Actuator SSL y observabilidad

El nuevo endpoint /actuator/info ahora expone información sobre los certificados SSL configurados: fechas de validez, emisor y sujeto.

properties
# application.properties
# Enable SSL information in actuator
management.info.ssl.enabled=true

# Configure warning threshold for expiring certificates
management.health.ssl.certificate-validity-warning-threshold=30d

Esta funcionalidad permite monitorear la expiración de certificados directamente via actuator, facilitando la automatización de renovaciones.

SslMonitoringConfig.javajava
@Configuration
public class SslMonitoringConfig {

    @Bean
    public HealthIndicator sslCertificateHealth(SslInfo sslInfo) {
        return () -> {
            // Custom certificate validity check
            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();
        };
    }
}

Para observabilidad, el transporte OTLP ahora soporta gRPC además de HTTP.

properties
# application.properties
# Use gRPC for OTLP (traces and metrics)
management.otlp.tracing.transport=grpc
management.otlp.tracing.endpoint=http://localhost:4317

# New: group applications together
spring.application.group=payment-services

El grupo de aplicación (spring.application.group) permite agrupar lógicamente múltiples servicios en dashboards de observabilidad.

Imágenes OCI más ligeras

El builder de imágenes OCI por defecto cambia de paketobuildpacks/builder-jammy-base a paketobuildpacks/builder-jammy-java-tiny, produciendo imágenes significativamente más pequeñas.

build.gradlegroovy
tasks.named("bootBuildImage") {
    // Native ARM support (new)
    imagePlatform = "linux/arm64"

    // New security flag
    trustBuilder = false

    // Image configuration
    imageName = "myregistry.com/myapp:${version}"
}

El nuevo parámetro imagePlatform simplifica las builds multiplataforma para ARM y x64.

xml
<!-- pom.xml -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <!-- Native ARM support -->
            <platform>linux/arm64</platform>
            <!-- Builder optimized for minimal images -->
            <builder>paketobuildpacks/builder-jammy-java-tiny</builder>
        </image>
    </configuration>
</plugin>

Estas imágenes optimizadas arrancan más rápido y consumen menos recursos, especialmente beneficioso para despliegues en Kubernetes.

Nota de migración

El cambio de builder puede afectar a aplicaciones que dependan de ciertas herramientas del sistema. Es recomendable probar las nuevas imágenes antes de desplegar en producción.

Cambios en Bean Validation

Spring Boot 3.4 alinea el comportamiento de validación con la especificación de Bean Validation. La validación ya no se propaga automáticamente a propiedades anidadas.

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

    @NotBlank
    private String name;

    // IMPORTANT: @Valid required to cascade validation
    @Valid
    private DatabaseConfig database;

    // Without @Valid, ServerConfig constraints will NOT be checked
    private ServerConfig server;

    // Getters and setters
}

public class DatabaseConfig {
    @NotBlank
    private String url;

    @Min(1)
    private int poolSize;

    // Getters and setters
}

public class ServerConfig {
    @NotNull // This constraint will NOT be checked without @Valid on parent
    private Integer port;

    // Getters and setters
}

Este cambio puede afectar aplicaciones existentes. Se recomienda auditar las clases @ConfigurationProperties y agregar @Valid donde la validación debe propagarse.

Deprecación de @MockBean y @SpyBean

Las anotaciones @MockBean y @SpyBean de Spring Boot están deprecadas en favor de las nuevas anotaciones de Mockito.

UserServiceTest.java (new approach)java
@SpringBootTest
class UserServiceTest {

    // New: native Mockito annotations
    @MockitoBean
    private UserRepository userRepository;

    @MockitoSpyBean
    private EmailService emailService;

    @Autowired
    private UserService userService;

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

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

        // Spy verification
        verify(emailService).sendWelcomeEmail("test@example.com");
    }
}

Las anotaciones antiguas siguen funcionando pero muestran advertencias de deprecación. Se recomienda planificar la migración a @MockitoBean y @MockitoSpyBean.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Conclusión

Spring Boot 3.4 ofrece mejoras sustanciales en productividad y observabilidad:

Checklist de migración:

  • Habilitar logging estructurado para mejorar la observabilidad
  • Verificar el comportamiento de graceful shutdown (ahora habilitado por defecto)
  • Considerar virtual threads para aplicaciones con alta carga de I/O (Java 21+)
  • Migrar a MockMvcTester para tests más legibles
  • Agregar @Valid en propiedades @ConfigurationProperties anidadas
  • Reemplazar @MockBean/@SpyBean por @MockitoBean/@MockitoSpyBean
  • Probar las nuevas imágenes OCI antes del despliegue en producción

Spring Boot 3.4 consolida su posición como el framework de referencia para el desarrollo Java moderno, con especial atención a los estándares de observabilidad y rendimiento.

Fuentes:

Etiquetas

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

Compartir

Artículos relacionados