Roadmap de Spring Boot y Java: Qué viene y cómo afecta al backend 

Spring Boot 4 y Java 25 obligan a revisar la plataforma completa: contratos JSON, seguridad, observabilidad, dependencias internas y comportamiento en runtime. La subida de versión merece tratarse como una migración de plataforma entera.

Este artículo se centra en una pregunta práctica: ¿Qué debo revisar antes de migrar a Spring Boot 4.1.x, cómo realizo la migración y qué cambios de Java se deben tener en cuenta para futuras migraciones?

    Artículo elaborado por:

    Jose Gracia Berenguer

    Analista Programador (I+D) @ edicom | FullStack developer | Ingeniero informático

    Apasionado Full-Stack Developer con un fuerte enfoque en aplicaciones web y desarrollo Back-end.
    Mi web: https://jgracia.es/

    Manuel Gregorio

    Scrum Master

    Scrum Master e ingeniero de software Backend especializado en Java y Spring. Graduado en Ingeniería Informática y Máster en Ingeniería y Tecnología de Sistemas Software. Autor de una publicación internacional en testing automatizado.

Contenidos

  1. Glosario
  2. Roadmap de Spring Boot
  3. Spring Boot 3.5
  4. Spring Boot 4.0
  5. Estrategia de migración a Spring Boot 4.1.x
  6. Jackson 3
  7. Seguridad, métricas y comportamiento silencioso
  8. Clientes HTTP declarativos, gRPC y SSRF
  9. APIs importantes “destacadas” a migrar
  10. Criterios para dar por terminada la migración
  11. Java y OpenJDK después de 25: qué nos espera
  12. Conclusión
  13. Referencias oficiales y blogs de interés

Glosario

A continuación, se describen algunos términos técnicos nombrados en el artículo. 

  • Heapdump: volcado de memoria de la JVM. Es útil para diagnosticar fugas de memoria, pero puede contener tokens, contraseñas, variables de entorno, datos de sesión o información interna. 
  • SSRF: Server-Side Request Forgery. Ocurre cuando un atacante consigue que un servidor haga peticiones HTTP hacia destinos que no debería, por ejemplo, localhost, redes privadas, metadatos cloud o servicios internos. 
  • Micrometer / Observation: capa de métricas, trazas y observabilidad que permite saber qué hace la aplicación en runtime, no solo si está viva. 

Roadmap de Spring Boot 

Antes de comenzar la migración a Spring Boot 4, conviene usar Spring Boot 3.5 como fase base de limpieza: resolver deprecaciones, revisar la exposición de Actuator y dejar la configuración preparada para entornos cloud. 

Spring Boot 4.0 es el cambio de plataforma: Spring Framework 7, Jakarta EE 11, Jackson 3, cambios en testing, modularización interna y nuevas reglas en autoconfiguración. 

Spring Boot 4.1.x consolida ese ciclo con capacidades relevantes como gRPC, mitigación SSRF y conexiones lazy a base de datos, siempre que se contrasten contra la versión concreta que se vaya a adoptar y sus notas de migración.

La cronología de versiones debe servir como mapa de lectura: primero Spring Boot 3.5 como fase de limpieza, después Spring Boot 4.0 como salto de plataforma y finalmente Spring Boot 4.1.x como consolidación de capacidades nuevas y revisión de riesgos de producción. En la siguiente tabla se puede apreciar el estado de soporte de las ramas de Spring Boot que se comentan.

Versión Spring Boot Estado de soporteBaseline de JavaEnfoque
3.4 / 3.5.xSoporte comercial / Mantenimiento activo17Madurez de observabilidad 
Seguridad en actuators 
4.0Soporte general activo 17 
recomendado 21+ 
Jakarta EE 11 
Clientes HTTP Declarativos y modularidad 
4.1.0 (actual) Actual 17 
recomendado 21+ 
gRPC Nativo 
Seguridad SSRF 
Optimización de arranque 

Spring Boot 3.5

Spring Boot 3.5 funciona como una versión de estabilización antes de abordar una migración seria a la 4.1.0. En esta fase interesa reducir incertidumbre: cerrar deprecaciones, revisar exposición de Actuator y limpiar configuración antes de sumar los cambios de plataforma que llegan con Spring Boot 4. 

Respecto a Actuator, el endpoint “/actuator/heapdump”, exponerlo sin control es peligroso porque un heap dump puede contener información sensible. La configuración antigua típica era demasiado permisiva ya que antes se publicaba más información de la necesaria, ahora se deben habilitar solo los endpoints imprescindibles y proteger de forma explícita los que puedan revelar estado interno, métricas sensibles o volcados de memoria.

El cambio importante es pasar de una exposición amplia a una configuración granular, donde solo se publica lo necesario y bajo controles diferenciados. En la imagen siguiente se expone un código donde se muestra el cambio; antes se incluía todo, ahora se debe definir qué se expone.

# Antes:
management.endpoints.web.exposure.include=*
# Después:
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.heapdump.access=none

Otra novedad interesante es la importación de configuración desde variables de entorno con “spring.config.import=env:”. Esto permite inyectar un bloque completo de propiedades, útil en Kubernetes o entornos donde la configuración llega desde ConfigMaps o Secrets.

Ejemplo para su configuración:

# indicar a spring que cargue la config desde la variable 'CONFIG_PROD_MASIVA'
spring.config.import=env:CONFIG_PROD_MASIVA
spring.config.import=optional:env:CONFIG_PROD_MASIVA
CONFIG_PROD_MASIVA='server.port=8080
feature.payments.enabled=true
spring.datasource.xxxx.maximum-pool-size=20'

En Kubernetes, la misma idea podría cargarse desde un ConfigMap o Secret gestionado por plataforma:

kubectl create configmap payments-config \
    --from-literal=CONFIG_PROD_MASIVA='server.port=8080
feature.payments.enabled=true
clients.billing.base-url=https://billing.internal'

 

En el gráfico siguiente se expone un flujo para la integración de este ConfigMap hasta llegar a Spring. Este lo incorpora al entorno de la aplicación y el servicio lo consume ya durante el runtime.

El patrón es útil en despliegues donde la configuración cambia por entorno, se deben imponer límites: separar secretos de configuración no sensible, evitar bloques enormes sin trazabilidad y documentar qué equipo controla cada valor. Su valor está en agrupar configuración de entorno de forma limpia, no en crear una caja negra difícil de auditar.

Spring Boot 4.0

La migración a Spring Boot 4.0 cambia la base sobre la que se ejecuta la aplicación. Arrastra Spring Framework 7, Jakarta EE 11, Jackson 3, ajustes de testing, cambios de autoconfiguración y una actualización importante del ecosistema.

El nuevo marco tecnológico de Spring Boot 4.0 se entiende mejor comparando la base anterior con la nueva: versiones de Spring Framework, Jakarta EE, Servlet, JPA, Kotlin, GraalVM, JUnit y Jackson condicionan qué dependencias compilan, qué starters siguen siendo válidos y qué comportamiento puede cambiar en runtime.

ComponenteSpring Framework 6.2Spring Framework 7.0
JDK17 baseline, recomendado 2117 baseline; última LTS recomendada para validación
Jakarta EE9/1011
Servlet6.06.1 (Tomcat 11, Jetty 12.1)
JPA3.13.2 (Hibernate 7.1/7.2)
Bean Validation3.03.1
Kotlin1.x/2.02.2
GraalVM22.3+25
JUnit56
Jackson2.x3.x default, (2.x deprecated)

Estrategia de migración a Spring Boot 4.1.x

La guía oficial de Spring Boot 4.0 recomienda estabilizar primero en la última 3.5.x, compilar, ejecutar tests y resolver todas las deprecaciones, porque lo marcado como deprecated en 3.x puede haber desaparecido en 4.0. En servicios reales, saltarse este paso mezcla demasiadas variables: deuda previa, dependencias antiguas, cambios de Jakarta, Jackson, tests rotos y diferencias de runtime.

La ruta recomendada es:

  • Estabilizar en la última 3.5.x y dejar build, tests e integración en verde.
  • Ejecutar la migración a 4.0.x y corregir compilación, arranque y comportamiento.
  • Consolidar en 4.1.x.

La clave está en respetar el orden del proceso: primero reducir deuda en 3.5.x y después abordar el cambio de plataforma con menos variables abiertas.

También se deben revisar requisitos de sistema y dependencias. Spring Boot 4 requiere Java 17 o superior, recomienda usar la LTS más reciente (25 en este punto), exige Kotlin 2.2+ si el proyecto usa Kotlin, GraalVM 25+ si se construyen native “images”, y se apoya en Jakarta EE 11, testing con JUnit 6. Esto impacta directamente en servidores embebidos y contenedores externos.

Cuando aparezcan fallos durante la migración, se debería localizar primero la capa afectada para que, si buscamos documentación, sepamos a cuál acudir:

  • Si el cambio está en “org.springframework.*”, probablemente viene de Spring Framework 7.
  • Si el cambio está en “org.springframework.boot.*”, probablemente viene de Spring Boot 4.
  • Si afecta a seguridad, testing, Jackson o servidor embebido, puede cruzar varias capas, por lo que habría que prestar especial atención y trazar el origen.

Los fallos de migración se pueden clasificar en tres grupos bien diferenciados:

El primer grupo es el de los fallos de compilación. Son molestos, pero son los más fáciles de abordar: el compilador dice dónde mirar. Aquí entran imports rotos, APIs eliminadas, “@MockBean” reemplazado por “@MockitoBean” como ejemplo.

El segundo grupo aparece en arranque. La aplicación compila, pero falla al levantar. Aquí aparecen starters no compatibles, autoconfiguraciones internas antiguas, dependencias que todavía usan mecanismos retirados o servidores embebidos no alineados con Servlet 6.1.

El tercer grupo es el peligroso: fallos silenciosos. La aplicación compila, arranca y pasa los tests, pero cambia comportamiento. Puede afectar a serialización JSON, reglas de seguridad, transacciones, métricas o inicialización de beans. Este es el tipo defallo que llega a producción si la migración se valida solo con “build verde”.

La migración debería gestionarse como un proceso con criterios de salida claros: compilar, arrancar, validar contratos, revisar seguridad, sobre todo en los endpoints y medir comportamiento bajo carga.

La receta de OpenRewrite Community ayuda con el trabajo mecánico inicial, pero debe ejecutarse en una rama controlada, revisar los cambios realizados y acompañarse de pruebas de contrato, seguridad e integración. Para lanzarla se puede usar el siguiente comando:

mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
  --define rewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:RELEASE \
  --define rewrite.activeRecipes=org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0 \
  --define rewrite.exportDatatables=true


La receta UpgradeSpringBoot_4_0 cubre más que una subida de versiones: incluye migraciones hacia Spring Framework 7, Spring Security 7, Spring Batch 6, Hibernate 7.1, Testcontainers 2, SpringDoc 3 y los starters modulares de Spring Boot 4. Sirve para el trabajo mecánico, aunque el diff generado necesita revisión. La migración se da por terminada cuando hay evidencia de que el comportamiento se mantiene; el build y tests en “verde” solo confirman una parte, hay que verificar el comportamiento de la aplicación.

Además, es necesario ajustar algunos nombres de librerías o imports que la receta de OpenRewrite no ha modificado, entre ellos:

org.apache.commons.lang3.StringUtils -> org.apache.commons.lang3.Strings
org.springframework.boot.actuate.health.* -> org.springframework.boot.health.contributor.*
com.fasterxml.jackson.* -> tools.jackson.*
java.nio.file.Paths -> java.nio.file.Path
org.springframework.lang.Nullable -> org.jspecify.annotations.Nullable
javax* -> jakarta*

En el caso de “com.fasterxml.jackson”, no todos los imports se deben cambiar a la vez: las anotaciones pueden mantenerse para evitar cambios innecesarios en los DTOs. Los cambios de imports se pueden revisar en la referencia del BOM de Jackson.

En Spring Security 7, el cambio de path matching puede alterar reglas de autorización que antes parecían estables. Además de comprobar el arranque, hay que validar rutas reales, orden de reglas y diferencias entre patrones antiguos y nuevos.

  • Cambia el motor a MVC/PathPattern
  • “/” final puede no comportarse igual
    • Comportamiento actual / Nuevo:
// AHORA (PathPattern - Spring Boot 4.1)
// "/api/orders" y "/api/orders/" son patrones DIFERENTES
@Bean
SecurityFilterChain afterConfig(HttpSecurity http) throws Exception {
    return http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/orders").hasRole("USER")
            .anyRequest().authenticated()
        )
        .build();
}
// El comportamiento CAMBIA:
// GET /api/orders     ✓ Protegido
// GET /api/orders/    ✘ NO protegido (PathPattern ve "/" como diferente)
  • Variable de ruta hace “matching” con el segmento real y no texto literal
# Antes:
- "/api/orders/{id}" podía tratarse como texto literal en algunos enfoques de matching.
- "/api/orders/" y "/api/orders" no siempre se comportaban igual.
# Ahora con MVC + PathPattern:
- "{id}" matchea un segmento real de la ruta.
- El slash final puede cambiar el resultado del match.
- Hay que revalidar reglas de autorización y tests de seguridad.
  • Orden de reglas es crítico (regla amplia puede «solapar» otras)

Esta revisión sirve para detectar reglas demasiado amplias, rutas que han cambiado de interpretación y diferencias entre el comportamiento histórico de la aplicación y el estándar actual. La forma más fiable de detectar estas diferencias es ejecutar tests de integración o E2E antes y después de la migración. Si las rutas críticas ya están cubiertas, cualquier cambio en autorización, CSRF o path matching será más fácil de detectar.

Para propiedades renombradas o eliminadas, Spring Boot ofrece “spring-boot-properties-migrator». Es una ayuda temporal: imprime diagnósticos al arrancar y puede adaptar propiedades en tiempo de ejecución durante la migración. Se debe eliminar al terminar la migración.

<!-- módulo temporal que actualiza application.properties/application.yml en runtime
     también pinta diagnósticos en el arranque -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-properties-migrator</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- eliminar al final de la migración -->

Spring Boot 4 introduce starters más específicos. Por ejemplo, “spring-boot-starter-web” sigue siendo el punto de entrada habitual para aplicaciones web, pero ciertas tecnologías pueden requerir starters explícitos. Existe una vía temporal con “spring-boot-starter-classic” y “spring-boot-starter-test-classic”, pensada para recuperar un classpath parecido al de Boot 3 mientras se corrigen imports y dependencias. Debe tratarse como un puente de migración, no como destino final.

Con respecto de la migración “javax.*” a “jakarta.*” ya empezó en Spring Boot 3, pero Spring Framework 7 endurece más este aspecto. Un caso especialmente delicado son anotaciones de ciclo de vida o inyección que pueden compilar y no comportarse igual si queda soporte antiguo en el “classpath”.

También deben revisarse las autoconfiguraciones internas y los starters propios. El patrón antiguo basado en “spring.factories” debe migrarse al mecanismo actual de “AutoConfiguration.imports”. Si este punto queda a medias, el fallo puede aparecer como una configuración que ya no se carga o como un bean que desaparece sin un error evidente. En el código inferior se muestra el cambio de ruta que se comentaba.

# Antes:
# FICHERO: META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.edicom.repository.sync.AuthAutoConfiguration

# Después:
# FICHERO: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.edicom.repository.sync.AuthAutoConfiguration

Hay más cambios de infraestructura que pueden parecer secundarios y no lo son. “HttpHeaders” en Spring Framework 7 deja de comportarse como un “MultiValueMap”, así que utilidades o filtros que lo traten como mapa deben migrar a la API específica de “headers”. En proyectos con “null-checkers”, “JSpecify” sustituye a anotaciones antiguas de “null-safety” y puede provocar errores de compilación o cambios de “nullability”.

Spring Boot 4 separa persistencia en nuevos módulos. Un ejemplo concreto es “@EntityScan”, que pasa hacia “org.springframework.boot.persistence.autoconfigure.EntityScan”. En aplicaciones multi-módulo, un “import” antiguo puede acabar pareciendo un problema de entidades no detectadas cuando la causa real es el cambio de autoconfiguración.

Jackson 3

Jackson 3 es uno de los puntos más sensibles de la migración. Aunque Jackson 2 puede seguir usándose como puente temporal, hay que planificar la migración a la nueva versión porque puede alterar contratos JSON que consumidores externos o internos dan por estables.

Jackson 2 puede funcionar como puente temporal. Tratarlo como destino final, en cambio, deja deuda técnica con fecha de caducidad.

La configuración de transición permite ajustar el comportamiento del mapper sin bloquear toda la migración, siempre que después se validen los contratos reales. En la configuración siguiente se muestra un ejemplo de configuración de Jackson donde en una aplicación conviven ambas versiones.

spring:
  application:
    name: xxx
  jackson:
    default-property-inclusion: non_empty
    serialization:
      fail-on-empty-beans: false
    deserialization:
      fail-on-ignored-properties: false
      fail-on-unknown-properties: false
      fail-on-null-for-primitives: false
    datatype:
      datetime:
        write-dates-as-timestamps: false
        write-durations-as-timestamps: false
    find-and-add-modules: true
# config solo para jackson 3
---
spring:
  config:
    activate:
      on-profile: jackson3
  jackson:
    datatype:
      enum:
        read-unknown-enum-values-as-null: false

Spring Boot 4 incorpora mecanismos explícitos para esa transición. La propiedad “spring.jackson.use-jackson2-defaults=true” acerca el comportamiento del mapper a los valores por defecto de Jackson 2, y el módulo “spring-boot-jackson2” permite ganar tiempo cuando el proyecto todavía no puede adoptar Jackson 3. Ambos recursos deben usarse como apoyo temporal: ayudan a reducir riesgo durante la migración, pero no eliminan la necesidad de validar contratos JSON y planificar el salto definitivo a Jackson 3.

Lo más crítico de la migración a Jackson 3 es el contrato que otros sistemas ya consumen. Fechas, enums, nulls, módulos personalizados, Optional, tipos polimórficos o serialización de errores pueden cambiar sin romper compilación, pero sí romper consumidores, snapshots de tests o integraciones que comparan payloads byte a byte.

Otro detalle importante: Spring Boot 4 autoconfigura “mappers” específicos por formato. Para JSON, el bean relevante pasa a ser “JsonMapper”; para XML, “XmlMapper”. Si antes se sustituía comportamiento definiendo un “ObjectMapper”, puede que ahora no baste. En personalizaciones avanzadas se debe revisar “JsonMapperBuilderCustomizer”, módulos registrados y propiedades movidas bajo “spring.jackson.json.*”.

Seguridad, métricas y comportamiento silencioso

La seguridad merece un extra de atención. Hay que probar POST, PUT, PATCH y DELETE con usuarios reales, roles distintos y rutas críticas. Cambios en CSRF, CORS o path matching pueden producir 403 inesperados o dejar rutas con una protección distinta a la esperada.

Tras la migración hay que forzar tráfico real y comprobar “/actuator/metrics”, trazas y logs correlacionados, también conviene revisar “liveness” y “readiness”. En Spring Boot 4, los grupos de “health” están habilitados por defecto. Esto es bueno para entornos “cloud”, pero obliga a revisar qué significa exactamente “ready” en cada aplicación. Un servicio puede estar vivo, pero no preparado para atender tráfico si todavía no ha inicializado caches, rutas, clientes o dependencias críticas.

En web MVC hay otro detalle: si se personalizan “converters HTTP”, “HttpMessageConverters” queda deprecado y la configuración se separa mejor entre cliente y servidor con customizers específicos.

Clientes HTTP declarativos, gRPC y SSRF

Con Spring Framework 7, los clientes HTTP declarativos basados en “@HttpExchange” reducen código repetitivo y concentran el contrato remoto en una interfaz. Esto resulta útil cuando una integración tiene muchos endpoints, se reutiliza en varios servicios o necesita una definición más fácil de revisar.

El cambio se entiende mejor comparando dos formas de modelar una integración HTTP: código imperativo repetido frente a una interfaz que concentra contrato, rutas y parámetros.

En el enfoque anterior, cada método suele repetir detalles de petición, respuesta y construcción de URL. Cuando una integración crece, esa repetición dificulta revisar el contrato real de la API.

Ahora el código puede simplificarse con “@HttpExchange”, que permite definir la URL base de una integración y completar cada operación con anotaciones como “@GetExchange”, “@PostExchange” o el método que corresponda. El tipo de retorno queda declarado en la firma del método, y parámetros como “@PathVariable” permiten construir la ruta de forma similar a un controlador REST de Spring.

Con clientes declarativos, la interfaz pasa a ser el contrato visible de la integración: se ve qué endpoints existen, qué parámetros reciben y qué devuelven sin recorrer código auxiliar.

Se puede ver un ejemplo en el código a continuación

// ANTES código imperativo
@Service
class CustomerClient {
    private final RestClient restClient;
    CustomerClient(RestClient.Builder builder) {
        this.restClient = builder
            .baseUrl("https://customers.internal")
            .build();
    }
    CustomerDto findById(Long id) {
        return restClient.get()
            .uri("/customers/{id}", id)
            .retrieve()
            .body(CustomerDto.class);
    }
}
// DESPUÉS
@HttpExchange("/customers")
interface CustomerClient {
    @GetExchange("/{id}")
    CustomerDto findById(@PathVariable Long id);
}

Como se puede apreciar el código ahora es mucho más limpio y legible, aprovecha de la interfaz para inferir el contrato, por ejemplo, el objeto que devuelve de la firma de la función en vez de tener que especificárselo manualmente.

Spring Boot 4.1 también refuerza el soporte para “gRPC”. REST sigue siendo excelente para “APIs” públicas y consumo amplio. “gRPC” encaja mejor en comunicación interna, contratos fuertemente tipados, streaming o baja latencia entre servicios controlados por la misma organización.

También hay que tener en cuenta SSRF. Aparece si el backend abre conexiones a partir de URLs controladas por usuario o por datos externos. En Spring Boot 4.1, InetAddressFilter permite bloquear rangos o destinos en clientes HTTP configurados; la defensa sigue requiriendo allowlist, DNS controlado, timeouts y pruebas negativas contra localhost, redes privadas y metadatos cloud.

En una aplicación que consume URLs recibidas desde fuera, la protección contra SSRF debe decidir qué destinos son válidos antes de abrir la conexión. El ejemplo visual contrasta una llamada directa con una llamada filtrada por allowlist, validación de rangos y límites de ejecución.

La diferencia práctica es que la aplicación deja de actuar como un proxy abierto. Solo se permiten destinos esperados, se bloquean redes internas no autorizadas y se añaden timeouts para evitar que una entrada maliciosa degrade el servicio.

Ese enfoque convierte la validación SSRF en una decisión explícita antes de salir a red: destino permitido, rangos bloqueados, DNS bajo control y límites de ejecución.

La protección real combina allowlist de hosts, bloqueo de rangos privados, DNS controlado, límites de tamaño, timeouts y políticas de red. El marco reduce superficie de error; la aplicación debe concretar qué destinos acepta y bajo qué condiciones.

En testing, la migración importa porque cambia cómo se preparan mocks, clientes de prueba y contextos de Spring. Si estos cambios se tratan como simples renombres, es fácil conservar tests verdes que ya no cubren el mismo comportamiento.

El reemplazo de “@MockBean” por “@MockitoBean” debe revisarse junto con el ciclo de vida del contexto de prueba, porque puede afectar a qué beans se sustituyen y cuándo se inicializan.

// ANTES MockMvc con @SpringBootTest
@SpringBootTest
class UserControllerIT {
    @Autowired
    MockMvc mockMvc;
}
// DESPUÉS
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIT {
    @Autowired
    MockMvc mockMvc;
}

MockMvc ya no debería darse por supuesto solo por usar “@SpringBootTest”. Si una prueba lo necesita, conviene declararlo de forma explícita con “@AutoConfigureMockMvc”.

En pruebas que levantan el servidor, “TestRestTemplate” también debe configurarse de forma explícita con “@SpringBootTest” y “@AutoConfigureTestRestTemplate”. La intención es que el test declare qué infraestructura necesita, en lugar de depender de efectos colaterales del contexto.

APIs importantes “destacadas” a migrar

En la tabla siguiente se muestra un resumen de algunas de las APIs que están “deprecadas” y merece la pena destacar para que, si las usamos o si lo deseamos, revisemos su documentación oficial de migración.

Deprecada en la 7.0Migrar a
Jackson 2Jackson 3
AntPathMatcher para request mappingsPathPattern
PathMatcher en Spring MVCPathPattern
RestTemplateRestClient
JUnit 4JUnit 6
<mvc:*> XML namespaceJava config

Criterios para dar por terminada la migración

Cada punto del siguiente checklist debería validarse contra un servicio real: qué endpoint o contrato afecta, qué prueba lo cubre, qué métrica confirma que no ha cambiado el comportamiento y qué plan de rollback existe si aparece una regresión.

El checklist final reúne los puntos donde una migración puede fallar, aunque compile: contratos JSON, reglas de seguridad, configuración, autoconfiguraciones internas, observabilidad y pruebas de integración.

Después de controlar los riesgos inmediatos de Spring Boot 4.1.x, hay que vigilar el JDK con criterio operativo: arranque, memoria, concurrencia, seguridad y observabilidad.

Una vez cerrada la migración de Spring Boot, el siguiente eje de decisión es la JVM sobre la que corre: qué cambia en Java 26/27 y qué de eso afecta a coste operativo hoy. En los siguientes puntos de este artículo, de manera adicional, se exponen los cambios de Java en las versiones futuras.

Java y OpenJDK después de 25: qué nos espera

Después de Java 25, la conversación deja de centrarse solo en nuevas APIs del lenguaje. Para backend, lo relevante es cómo evoluciona la JVM: arranque, warmup, memoria por objeto, concurrencia, observabilidad y seguridad del stack TLS.

Java 26 y Java 27 se enfocan en qué costes quiere reducir la JVM, qué áreas de seguridad se están reforzando y qué partes del runtime pueden cambiar la forma de operar servicios en los próximos ciclos.

Java 26 fue publicado en marzo de 2026. Incluye, entre otras JEPs, AOT Object Caching con cualquier GC, HTTP/3 para el HTTP Client API y mejoras de G1.

  • HTTP/3 para el HTTP Client API.
  • Ahead-of-Time Object Caching con cualquier “Garbage Collector”.
  • Mejoras de rendimiento en G1 (el Garbage Collector por defecto de la JVM).
  • Evolución del modelo de concurrencia y del lenguaje sin tratarlos todavía como base estable.
  • Limpieza y seguridad de plataforma: eliminación definitiva del Applet API, PEM.

Java 27 está previsto para septiembre del 2026. Cualquier lista de características debe leerse como planificación sujeta al estado final de JEPs y notas oficiales.

  • Compact Object Headers por defecto, la materialización más visible del project Lilliput de OpenJDK: menos coste por objeto y una JVM potencialmente más eficiente en aplicaciones con millones de objetos pequeños.
  • G1 como recolector por defecto en todos los entornos, simplificando el comportamiento esperado de la JVM y evitando diferencias entre configuraciones.
  • Post-Quantum Hybrid Key Exchange para TLS 1.3, relevante para comunicaciones seguras, clientes HTTP, mTLS y requisitos de compliance.
  • JFR In-Process Data Redaction, útil para observabilidad en entornos donde las trazas no deben exponer datos sensibles.
  • Structured Concurrency, Lazy Constants, Vector API y patrones con tipos primitivos siguen avanzando en preview o incubator; son señales de dirección, no una invitación a meter todo en producción sin revisar flags y soporte.

OpenJDK organiza algunas de sus líneas de evolución como “Projects”, esfuerzos técnicos que maduran antes de llegar como cambios visibles al JDK. Project Lilliput es un buen ejemplo: trabaja en reducir el coste de las cabeceras de objeto para que aplicaciones con muchos objetos pequeños puedan usar menos memoria sin cambiar su modelo de programación.

Project Leyden apunta a mejorar el arranque, el warmup y la predictibilidad de ejecución. El objetivo no es solo que una aplicación arranque rápido, sino reducir el tiempo hasta que el servicio está realmente preparado para recibir tráfico. En entornos cloud como Kubernetes, esto es especialmente relevante.

Project Lilliput trabaja sobre cabeceras de objeto más compactas. Cada objeto Java tiene metadatos internos además de sus campos. En aplicaciones con millones de objetos pequeños, ese coste se acumula: DTOs, entidades, eventos, mapas, listas, wrappers, objetos de serialización o estructuras intermedias creadas por frameworks.

El valor de Lilliput está en permitir que la JVM ejecute la misma semántica con menos coste de memoria; el desarrollador no tiene que cambiar el código de un día para otro para beneficiarse de esa línea de trabajo.

Project Valhalla apunta a modelos de datos más densos mediante value objects o “primitive classes”. La idea simplificada es poder representar ciertos datos como valores, con menos coste de identidad, indirección y referencia. Esto puede afectar a dominios con muchas estructuras pequeñas. También hay movimiento en seguridad. Java empieza a prepararse para criptografía post-cuántica e intercambios híbridos en TLS. Para muchos equipos no será una API que se toque directamente, pero sí afectará a servicios que dependen del stack TLS del JDK, clientes HTTP, librerías internas, mTLS, compliance o comunicaciones entre servicios.

La lectura práctica es sencilla: Leyden afecta a arranque y warmup; Lilliput, al coste por objeto; Valhalla, a modelos de datos más densos; y las líneas de seguridad del JDK, al stack TLS y a los requisitos de compliance. No todas requieren acción inmediata, pero sí se tendría que saber cuáles pueden cambiar decisiones de plataforma a medio plazo.

En la cronología siguiente se muestra un resumen de las versiones Java destacadas en este artículo.

Conclusión

La migración se da por terminada cuando hay evidencia de que el comportamiento se mantiene, no cuando el proyecto compila y los tests pasan en verde. Compilar confirma que el código es sintácticamente válido; no confirma que los contratos JSON sigan siendo los mismos, que las reglas de autorización protejan lo que deben proteger, ni que los endpoints críticos respondan igual bajo tráfico real.

El camino recorrido en este documento estabilizar en Spring Boot 3.5, saltar de plataforma en 4.0 y consolidar en 4.1 solo tiene sentido si en cada etapa se valida explícitamente lo que puede romperse en silencio.

Spring Boot 4 y Java 25+ representan una oportunidad real de modernización: mejor seguridad por defecto (SSRF, gestión de Actuator), mejoras de rendimiento y arranque heredadas de las últimas versiones de Java, y un modelo de desarrollo más limpio con clientes HTTP declarativos y soporte nativo para gRPC. Pero esa oportunidad solo se materializa si se trata como lo que es: una migración de plataforma completa, no una subida de versión de una dependencia más.

Referencias y blogs de interés