Día 12 de 30 Fase 3: El Salto a Spring Boot 3 y APIs REST

El Contenedor de Beans de Spring Boot 3 e Inyección Idiomática

ApplicationContext, Component Scanning, Requisitos de Java 17 y Compatibilidad con Java 21

Para dominar. Al terminar deberías poder hacerlo sin ayuda.

  • 3.5h de estudio
  • 5 conceptos
  • 3 preguntas
  • 1 katas
Java 17 LTSJava 21 LTSSpring Boot 3ApplicationContextBeans@Service@ConfigurationProperties
Al terminar este día sabrás 4
  • Explicar por qué Spring Boot 3 exige Java 17 y qué cambió con Jakarta EE.
  • Describir el ciclo de vida de un bean, desde el escaneo hasta su destrucción.
  • Declarar beans propios y de terceros con inyección por constructor idiomática.
  • Leer configuración tipada e inmutable con @ConfigurationProperties y records.

Spring Boot 3 Exige Java 17 LTS como Base Mínima

Spring Boot 3 abandonó Java 8 y 11. El estándar empresarial mínimo de Spring hoy es Java 17, permitiendo usar records y características modernas en todo el framework.

En una línea En pom.xml: <java.version>17</java.version> o <java.version>21</java.version>.

Comprobar versiones en arranque java
@SpringBootApplication
public class TaskFlowApplication {

    public static void main(String[] args) {
        SpringApplication.run(TaskFlowApplication.class, args);
    }

    @Bean
    ApplicationRunner informarVersiones(Environment env) {
        return args -> {
            System.out.println("Java " + Runtime.version());
            System.out.println("Spring Boot " + SpringBootVersion.getVersion());
            System.out.println("Hilos virtuales: " + env.getProperty("spring.threads.virtual.enabled", "false"));
        };
    }
}
Cómo funciona por dentro

Spring Boot 3 está construido sobre Spring Framework 6, que fijó Java 17 como versión mínima. No es un capricho: el código del framework usa records, sealed, text blocks y otras APIs del JDK 17, y se compila a bytecode de versión 61. Una JVM 11 no puede ni cargar esas clases.

Esa base permitió otros cambios de fondo: el salto a Jakarta EE 9+ (paquetes jakarta.*), la compilación anticipada (AOT) para generar imágenes nativas con GraalVM, la observabilidad con Micrometer integrada y, a partir de Spring Boot 3.2 con Java 21, el soporte de Virtual Threads.

En la práctica, en un proyecto nuevo se elige Java 21 (la LTS más reciente con soporte amplio) y Spring Boot 3.x. Java 17 sigue siendo el mínimo y es lo habitual en empresas que aún no han migrado.

Si aplicas a un puesto de Spring Boot 3, debes dominar como mínimo Java 17.

Se conecta con Jakarta EESpring Framework 6Migración

Cuatro bloques cronometrados. No pases al siguiente sin tener el resultado esperado.

  1. Bloque 1: Arquitectura Spring Boot 3 (0 - 25 min)

    Qué estudiar. ApplicationContext, Bean lifecycle, y el requisito de Java 17/21.

    Pregunta a responder. ¿Por qué @SpringBootApplication agrupa 3 anotaciones clave?

    Resultado esperado. Proyecto en start.spring.io con Spring Boot 3.2+ y Java 17 o 21 verificado.

  2. Bloque 2: Inyección por Constructor Idiomática (25 - 50 min)

    Qué estudiar. Creación de capas Controller -> Service -> Repository sin un solo @Autowired.

    Pregunta a responder. ¿Cómo resuelve Spring las dependencias cuando hay una única interfaz y dos implementaciones (@Primary, @Qualifier)?

    Resultado esperado. Servicio inyectado limpiamente con variables final.

  3. Bloque 3: Configuración con Records (50 - 80 min)

    Qué estudiar. Uso de @ConfigurationProperties vinculado a un record.

    Pregunta a responder. ¿Cómo leer propiedades de application.yml de forma inmutable y fuertemente tipada?

    Resultado esperado. Record de configuración tipado y validado en el arranque.

  4. Cierre y Consolidación (80 - 90 min)

    Qué estudiar. Repaso activo del ciclo de vida de los beans.

    Pregunta a responder. Explicar qué hace @PostConstruct y @PreDestroy.

    Resultado esperado. Notas de estudio registradas en la app local.

Tres formas de comprobar si lo has entendido, de menos a más exigente: explicarlo con tus palabras, responder preguntas trampa y resolver el reto del tutor.

Explícalo desde cero

Lee cada ficha y explícala en voz alta antes de seguir: si puedes contárselo a alguien que empieza, lo has entendido.

Cómo encuentra Spring tus Beans (@ComponentScan)

Lo que debes entender

@SpringBootApplication contiene @ComponentScan. Al arrancar, escanea el paquete actual hacia abajo buscando @Component, @Service, @Repository y @RestController para registrarlos en el grafo de dependencias.

Analogía para contarlo

Un radar que solo barre el piso en el que está colocado y los pisos inferiores del edificio; si pones un objeto en la terraza superior, no lo detecta.

Lo que suele explicarse mal

Pensar que Spring "adivina" o busca en todo el disco duro. Si creas una clase en un paquete superior fuera de la jerarquía de la clase Application, Spring no la verá.

Señal de que aún no lo dominas

Si no sabes por qué Spring lanza NoSuchBeanDefinitionException al mover una clase de paquete.

Preguntas trampa

Responde de memoria, en voz alta o por escrito, y después abre la respuesta razonada: lo importante es el porqué, no la palabra exacta.

Nivel 1 (Básico) ¿Por qué Spring Boot 3 no permite arrancar aplicaciones con Java 8 o Java 11? Ver respuesta

Respuesta razonada

Porque las clases de Spring Framework 6 y Spring Boot 3 están compiladas para Java 17 (bytecode versión 61). Una JVM 8 (versión 52) o 11 (versión 55) se niega a cargarlas y la aplicación falla nada más arrancar con UnsupportedClassVersionError. No es una comprobación artificial: el framework usa internamente APIs y características que no existen en esas versiones.

Subir la versión mínima permitió además simplificar el código del framework, adoptar Jakarta EE 9+ y habilitar AOT y GraalVM. Si un sistema está atado a Java 11, debe quedarse en Spring Boot 2.7, que ya no tiene soporte gratuito.

Qué está evaluando la pregunta

Porque Spring Framework 6 fue compilado con target de Java 17 y migró la especificación corporativa a Jakarta EE 10, requiriendo un JDK 17 o superior como baseline.

Nivel 2 (Intermedio) ¿Qué diferencia práctica hay entre @Component, @Service y @Repository en Spring? Ver respuesta

Respuesta razonada

Las cuatro son estereotipos: anotaciones meta-anotadas con @Component, así que el escaneo las detecta igual y todas crean un bean singleton. La diferencia está en la intención y en algún comportamiento extra.

@Service es puramente semántica: marca lógica de negocio y no añade comportamiento. @Repository sí añade algo: el PersistenceExceptionTranslationPostProcessor envuelve el bean para traducir excepciones nativas de JPA, Hibernate o JDBC a la jerarquía DataAccessException de Spring. @Controller y @RestController hacen que Spring MVC busque en ellos métodos @GetMapping, @PostMapping, etc. Usar el estereotipo correcto documenta la arquitectura y permite aplicar aspectos por capa.

Qué está evaluando la pregunta

En tiempo de ejecución son prácticamente iguales (todas son especializaciones de @Component), pero @Repository añade traducción automática de excepciones de base de datos a DataAccessException, y @Service comunica semántica de capa de negocio.

Nivel 3 (Entrevista Senior) ¿Cómo activas Virtual Threads de Java 21 en una aplicación Spring Boot 3.2+ y qué cambia internamente en Tomcat? Ver respuesta

Respuesta razonada

Con Java 21 y Spring Boot 3.2 o superior basta con spring.threads.virtual.enabled=true en la configuración. Internamente, Tomcat deja de usar su pool de hilos de plataforma (200 por defecto) y pasa a usar un ejecutor que crea un hilo virtual por petición. También se configuran con hilos virtuales el TaskExecutor de @Async, el planificador de @Scheduled y los listeners de Kafka y RabbitMQ.

El efecto es que las peticiones que esperan a E/S (base de datos, llamadas HTTP) ya no bloquean un hilo real, así que la aplicación aguanta muchas más peticiones concurrentes sin aumentar memoria. Hay que vigilar tres cosas: el pinning por bloques synchronized que hacen E/S (mejorado en Java 24), el abuso de ThreadLocal (cada hilo virtual tiene el suyo) y que el pool de conexiones a la base de datos pasa a ser el verdadero límite.

Qué está evaluando la pregunta

Con spring.threads.virtual.enabled=true. Internamente, el TomcatProtocolHandler sustituye su pool de hilos de plataforma por un Executors.newVirtualThreadPerTaskExecutor(), atendiendo cada petición HTTP en un Virtual Thread efímero.

Reto del tutor

Un desarrollador júnior dice: "Tengo un bean con @Scope("prototype") y otro con @Scope("singleton"). Si inyecto el Prototype dentro del Singleton, cada petición web obtendrá una instancia nueva del Prototype". ¿Es correcto?

Lo que una respuesta suele tener bien

Reconoce que Prototype genera instancias nuevas al solicitarse al contenedor.

Lo que suele quedarse en la superficie

Decir que "sí, porque es prototype".

El concepto de fondo que suele faltar

No entender que el Singleton solo se instancia UNA vez al arrancar la app; en ese momento se le inyecta el Prototype y ese mismo Prototype quedará fijado para siempre dentro del Singleton.

Cómo lo respondería alguien senior en una entrevista

Es completamente FALSO (el clásico "problema del bean scoping"). El Singleton se inicializa una sola vez; por tanto, la inyección del Prototype ocurre una sola vez. Cada petición web usará la misma instancia inyectada al inicio. Para resolverlo se debe usar un ObjectProvider<MiPrototype>, @Lookup o rediseñar la arquitectura evitando beans con estado mutable.

Proyecto «Pedidos» · paso 12 de 30

Pasar a Spring Boot 3

Dejar que Spring cree y conecte los componentes que hasta ahora ensamblabas a mano.

De qué va el proyecto

El backend de una tienda online: clientes, catálogo de productos y pedidos con sus líneas, pagos y envíos. Empieza como un proyecto Maven con records y termina como un sistema con API REST, seguridad, mensajería, contenedores y observabilidad. Cada paso construye sobre el anterior, así que trabaja siempre en el mismo repositorio.

Qué hacer

  1. Convierte el proyecto en una aplicación Spring Boot 3 y elimina el ensamblado manual del main.
  2. Mueve las comisiones de cada medio de pago a application.yml y léelas con @ConfigurationProperties sobre un record.
  3. Crea los perfiles dev (H2) y prod (PostgreSQL).

Está listo cuando

  • La aplicación arranca con ./mvnw spring-boot:run y el perfil dev sin configuración extra.
  • Ningún componente usa @Autowired sobre campos.

Katas: ejercicios cortos

Reglas de oro

  • Usa siempre inyección por constructor sobre campos private final; evita @Autowired en atributos.
  • Importa siempre jakarta.*, jamás el obsoleto javax.*.
  • En Java 21 activa Virtual Threads para maximizar el throughput HTTP de Tomcat.

Java 17 frente a Java 21

CaracterísticaJava 17 LTSJava 21 LTS
Versión mínima requerida Obligatoria en Spring Boot 3 Completamente soportada
Virtual Threads integrados No disponible Disponible con spring.threads.virtual.enabled=true
Se guarda automáticamente

Fuentes oficiales

Revisado el . Este día en Markdown