Llega el momento de escribir código. En esta lección generarás el proyecto ciclourbana con Spring Initializr —tanto desde la web como desde la línea de comandos—, lo abrirás en tu IDE y crearás el primer endpoint real de la plataforma: GET /api/v1/estaciones, que devolverá la lista de estaciones de la red de Ribalta. Después lo ejecutarás de tres maneras distintas, lo probarás con curl y lo empaquetarás en un JAR ejecutable que se ejecuta con java -jar sin instalar ningún servidor. Al terminar tendrás una API funcionando de principio a fin.

Contenido

  1. Spring Initializr: qué es y cómo se usa
  2. Generar el proyecto desde la web
  3. Generar el proyecto desde la línea de comandos
  4. Importar el proyecto en el IDE
  5. La clase de arranque generada
  6. El primer endpoint: GET /api/v1/estaciones
  7. Ejecutar la aplicación
  8. Probar el endpoint con curl
  9. Empaquetar y ejecutar el JAR
  10. Spring Boot DevTools y el reinicio automático
  11. Errores Comunes y Consejos
  12. Ejercicios

  1. Spring Initializr: qué es y cómo se usa

Spring Initializr (start.spring.io) es el generador oficial de proyectos Spring Boot. Le indicas cuatro datos y una lista de dependencias, y te devuelve un ZIP con:

  • Un pom.xml ya configurado con el parent de Spring Boot y las dependencias elegidas.
  • La clase principal de la aplicación, anotada y lista para arrancar.
  • Una clase de prueba básica.
  • El wrapper de Maven (mvnw, mvnw.cmd, .mvn/).
  • Un .gitignore adecuado y un application.properties vacío.

Es la forma correcta de empezar cualquier proyecto Spring Boot. Crear la estructura a mano es posible, pero es trabajo innecesario y propenso a errores.

  1. Generar el proyecto desde la web

Abre https://start.spring.io y rellena el formulario con estos valores exactos, que usaremos durante todo el curso:

Campo Valor Por qué
Project Maven Decidido en la lección 01-02
Language Java —
Spring Boot La última 3.x estable ofrecida Evita las versiones marcadas SNAPSHOT o M1 (preliminares)
Group com.ciclourbana Identifica a la organización
Artifact ciclourbana Nombre del proyecto y del JAR
Name ciclourbana Genera la clase CiclourbanaApplication
Description Gestión de la red de bicicletas eléctricas de Ribalta Metadato del pom.xml
Package name com.ciclourbana Importante: el paquete raíz del proyecto
Packaging Jar JAR ejecutable con servidor embebido
Java 21 Versión LTS del curso

En el panel Dependencies, pulsa "ADD DEPENDENCIES" y añade exactamente dos:

  • Spring Web — Spring MVC, Jackson para JSON y Tomcat embebido. Es lo que convierte el proyecto en una aplicación web.
  • Spring Boot DevTools — reinicio automático al cambiar código y desactivación de cachés en desarrollo.

Pulsa GENERATE. Se descarga ciclourbana.zip. Descomprímelo donde guardes tus proyectos.

Un consejo antes de descargar: el botón EXPLORE muestra el contenido del proyecto sin bajarlo, y SHARE genera una URL que reproduce exactamente esa configuración. Es muy útil para compartir el punto de partida con un compañero.

  1. Generar el proyecto desde la línea de comandos

Spring Initializr también es una API HTTP. Esto permite generar el proyecto sin abrir el navegador, y —más importante— dejar el comando guardado en la documentación del equipo para que cualquiera reproduzca el mismo punto de partida.

curl https://start.spring.io/starter.zip \
  -d type=maven-project \
  -d language=java \
  -d bootVersion=3.3.5 \
  -d groupId=com.ciclourbana \
  -d artifactId=ciclourbana \
  -d name=ciclourbana \
  -d description="Gestion de la red de bicicletas electricas de Ribalta" \
  -d packageName=com.ciclourbana \
  -d packaging=jar \
  -d javaVersion=21 \
  -d dependencies=web,devtools \
  -o ciclourbana.zip

# Descomprimir en una carpeta con el nombre del proyecto
unzip ciclourbana.zip -d ciclourbana
cd ciclourbana

Desglose de las opciones de curl:

  • -d clave=valor envía un parámetro de formulario. Al usar -d, curl hace automáticamente una petición POST.
  • dependencies=web,devtools es la lista de identificadores de dependencias separados por comas. web corresponde a "Spring Web" y devtools a "Spring Boot DevTools".
  • -o ciclourbana.zip guarda la respuesta en un fichero en vez de volcarla a la terminal.

Dos comandos exploratorios muy útiles:

# Ver toda la configuración disponible: versiones, dependencias e identificadores
curl https://start.spring.io

# Consultar los metadatos en JSON (versiones de Boot disponibles, etc.)
curl -H "Accept: application/json" https://start.spring.io | head -40

Si omites bootVersion, Initializr usa la última estable, que es lo habitualmente deseable.

  1. Importar el proyecto en el IDE

El proyecto es un proyecto Maven estándar, así que cualquier IDE lo entiende.

IntelliJ IDEA: File → Open y selecciona la carpeta ciclourbana (no el pom.xml suelto, aunque también funciona). IntelliJ detecta el pom.xml, indexa el proyecto y descarga las dependencias. Verás una barra de progreso abajo a la derecha; espera a que termine.

Eclipse / STS: File → Import → Maven → Existing Maven Projects, selecciona la carpeta raíz y pulsa Finish.

VS Code: File → Open Folder sobre ciclourbana. La extensión de Java arranca sola y aparece un panel Spring Boot Dashboard en la barra lateral.

En los tres casos, la primera importación tarda: Maven descarga las dependencias de Spring a ~/.m2/repository. Son unos cientos de megabytes la primera vez y prácticamente instantáneo a partir de entonces.

  1. La clase de arranque generada

Abre src/main/java/com/ciclourbana/CiclourbanaApplication.java. Contiene esto:

package com.ciclourbana;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CiclourbanaApplication {

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

Son diez líneas, pero cada una importa:

  • @SpringBootApplication es la anotación que lo activa todo. Agrupa tres anotaciones que estudiaremos en el módulo 2: habilita la autoconfiguración, marca la clase como fuente de configuración y activa el escaneo de componentes a partir de este paquete.
  • main es un método main de Java corriente. Una aplicación Spring Boot es, literalmente, un programa Java que arranca por su main. No hay contenedor externo que la invoque.
  • SpringApplication.run(...) crea el contexto de Spring, registra los beans y —al detectar Spring Web— arranca Tomcat. Devuelve el contexto ya en marcha. Lo que ocurre dentro, paso a paso, es el tema de la lección 01-05.
  • El argumento args se propaga: permite pasar parámetros desde la línea de comandos, como --server.port=9090.

Por coherencia con el resto del curso, renombra la clase a CicloUrbanaApplication (con la U mayúscula). Usa la función de renombrado del IDE (Shift+F6 en IntelliJ) para que actualice también el fichero de prueba.

  1. El primer endpoint: GET /api/v1/estaciones

Vamos a exponer las estaciones de Ribalta. Necesitamos dos piezas: un tipo que represente una estación y un controlador que la devuelva en JSON.

El record Estacion

Java 21 ofrece los records, ideales para datos inmutables. Crea el fichero src/main/java/com/ciclourbana/estaciones/Estacion.java:

package com.ciclourbana.estaciones;

/**
 * Representa una estación de anclaje de la red de Ribalta.
 * De momento es un simple record en memoria; en el módulo 4
 * se convertirá en una entidad JPA persistida en base de datos.
 */
public record Estacion(
        Long id,
        String nombre,
        String direccion,
        int capacidad,
        double latitud,
        double longitud
) {
}

Por qué un record y no una clase normal:

  • Genera automáticamente el constructor, los métodos de acceso (nombre(), capacidad()...), equals, hashCode y toString.
  • Es inmutable: una vez creado no cambia, lo que evita errores en entornos concurrentes.
  • Jackson, la librería que Spring Boot usa para convertir objetos a JSON, entiende los records desde hace varias versiones. Cada componente se convierte en una propiedad JSON con el mismo nombre.

El controlador

Crea src/main/java/com/ciclourbana/estaciones/EstacionController.java:

package com.ciclourbana.estaciones;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/v1/estaciones")
public class EstacionController {

    // Datos fijos en memoria. En el módulo 4 vendrán de PostgreSQL.
    private static final List<Estacion> ESTACIONES = List.of(
            new Estacion(1L, "Plaza Mayor",     "Plaza Mayor, 1",          24, 40.4168, -3.7038),
            new Estacion(2L, "Estación Norte",  "Avenida de la Estación 3", 30, 40.4290, -3.7020),
            new Estacion(3L, "Parque del Río",  "Paseo Fluvial 12",         18, 40.4105, -3.6950),
            new Estacion(4L, "Universidad",     "Campus Sur, acceso B",     36, 40.4402, -3.7255)
    );

    @GetMapping
    public List<Estacion> listarEstaciones() {
        return ESTACIONES;
    }
}

Qué hace cada anotación, en su nivel justo para esta lección:

  • @RestController marca la clase como componente de Spring que atiende peticiones HTTP y cuyos métodos devuelven directamente el cuerpo de la respuesta (no el nombre de una vista HTML). Spring detecta la clase al escanear el paquete y la registra.
  • @RequestMapping("/api/v1/estaciones") fija la ruta base de todos los métodos de la clase. Así el prefijo se escribe una sola vez.
  • @GetMapping sin argumentos asocia el método a GET sobre la ruta base exacta, es decir GET /api/v1/estaciones.
  • El método devuelve un List<Estacion>. Spring Boot, gracias a Jackson, lo convierte automáticamente a un array JSON y fija la cabecera Content-Type: application/json. No hay que serializar nada a mano.

El detalle de estas anotaciones, la negociación de contenido, los códigos de estado y el manejo de parámetros se tratan en profundidad en el módulo 3. Aquí nos basta con que funcione.

Cómo encajan las piezas

sequenceDiagram
    participant C as curl
    participant T as Tomcat embebido
    participant D as DispatcherServlet
    participant E as EstacionController
    participant J as Jackson

    C->>T: GET /api/v1/estaciones
    T->>D: Petición HTTP
    D->>D: Buscar el método que atiende la ruta
    D->>E: listarEstaciones()
    E-->>D: Lista de Estacion
    D->>J: Serializar a JSON
    J-->>D: Array JSON de estaciones
    D-->>T: 200 OK + cuerpo JSON
    T-->>C: Respuesta HTTP

  1. Ejecutar la aplicación

Hay tres formas, y conviene conocer las tres.

Con el plugin de Maven

Es la forma canónica desde la terminal:

./mvnw spring-boot:run

spring-boot:run compila el proyecto y arranca la aplicación en la misma JVM que Maven, sin necesidad de empaquetar. Es lo más cómodo durante el desarrollo.

La salida termina con algo parecido a:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

2026-08-31T10:22:41.118+02:00  INFO 18422 --- [ciclourbana] [  restartedMain] c.c.CicloUrbanaApplication : Starting CicloUrbanaApplication using Java 21.0.5
2026-08-31T10:22:42.905+02:00  INFO 18422 --- [ciclourbana] [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2026-08-31T10:22:42.918+02:00  INFO 18422 --- [ciclourbana] [  restartedMain] c.c.CicloUrbanaApplication : Started CicloUrbanaApplication in 2.174 seconds

Las tres líneas clave: la versión de Java usada, el puerto 8080 en el que escucha Tomcat y el tiempo de arranque. Interpretaremos este log línea a línea en la lección 01-05.

Para detener la aplicación, Ctrl+C.

Desde el IDE

Abre CicloUrbanaApplication y ejecuta el método main como cualquier programa Java: el triángulo verde en IntelliJ, Run As → Java Application en Eclipse, o Run sobre el main en VS Code.

La ventaja del IDE es el depurador: puedes poner un punto de interrupción en listarEstaciones() y ver la ejecución detenerse cuando llegue la petición.

STS y VS Code ofrecen además un Spring Boot Dashboard para arrancar, parar y reiniciar sin buscar la clase.

Desde el JAR empaquetado

Es la forma de producción, y la vemos en el apartado 9.

  1. Probar el endpoint con curl

Con la aplicación en marcha, abre otra terminal:

curl http://localhost:8080/api/v1/estaciones

Respuesta:

[{"id":1,"nombre":"Plaza Mayor","direccion":"Plaza Mayor, 1","capacidad":24,"latitud":40.4168,"longitud":-3.7038},{"id":2,"nombre":"Estación Norte",...}]

Para verlo legible y con las cabeceras:

# Con cabeceras y código de estado
curl -i http://localhost:8080/api/v1/estaciones

# Formateado con jq (si lo tienes instalado)
curl -s http://localhost:8080/api/v1/estaciones | jq

La respuesta con -i empieza así:

HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 31 Aug 2026 08:24:11 GMT

Observa que no has escrito una sola línea sobre JSON ni sobre el código 200. Spring Boot ha deducido el Content-Type a partir del tipo de retorno y ha aplicado el 200 por defecto. Esa es la autoconfiguración trabajando.

Prueba también una ruta inexistente para ver el comportamiento por defecto ante errores:

curl -i http://localhost:8080/api/v1/bicicletas
# HTTP/1.1 404
# {"timestamp":"...","status":404,"error":"Not Found","path":"/api/v1/bicicletas"}

Spring Boot devuelve un cuerpo de error estructurado sin que hayas configurado nada. Personalizarlo es el objetivo de la lección 03-06.

  1. Empaquetar y ejecutar el JAR

Construir el artefacto

./mvnw clean package

Este comando limpia la carpeta target, compila, ejecuta las pruebas y genera el JAR. Al terminar:

ls -lh target/*.jar
# -rw-r--r-- 1 usuario usuario  22M  ciclourbana-0.0.1-SNAPSHOT.jar
# -rw-r--r-- 1 usuario usuario  12K  ciclourbana-0.0.1-SNAPSHOT.jar.original

Aparecen dos ficheros, y la diferencia lo explica todo:

  • .jar.original (12 KB) es el JAR que produce Maven de forma estándar: solo tus clases compiladas.
  • .jar (22 MB) es el fat jar o uber jar: tus clases más todas las dependencias —Spring, Jackson, Tomcat— más un cargador especial.

El spring-boot-maven-plugin es quien realiza esa transformación en la fase package.

Qué es un JAR ejecutable y un fat jar

Un JAR normal es un ZIP con clases .class. Para ejecutarlo hacen falta todas sus dependencias en el classpath, lo que obliga a distribuir decenas de ficheros o a instalar un servidor de aplicaciones.

Spring Boot resuelve esto con un formato ingenioso:

ciclourbana-0.0.1-SNAPSHOT.jar
├── META-INF/
│   └── MANIFEST.MF          ← declara Main-Class y Start-Class
├── org/springframework/boot/loader/   ← el cargador de Spring Boot
├── BOOT-INF/
│   ├── classes/             ← TUS clases compiladas
│   │   └── com/ciclourbana/...
│   └── lib/                 ← TODAS las dependencias, como .jar anidados
│       ├── spring-web-6.x.jar
│       ├── tomcat-embed-core-10.x.jar
│       └── jackson-databind-2.x.jar
└── ...

Puedes verificarlo:

unzip -l target/ciclourbana-0.0.1-SNAPSHOT.jar | head -20
unzip -p target/ciclourbana-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF

El manifiesto muestra la clave del truco:

Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.ciclourbana.CicloUrbanaApplication

Cuando ejecutas el JAR, Java invoca el JarLauncher de Spring Boot, no tu clase. Ese lanzador instala un cargador de clases capaz de leer JAR anidados dentro de BOOT-INF/lib —algo que Java estándar no sabe hacer— y solo entonces llama a tu Start-Class.

La consecuencia práctica es enorme: un único fichero contiene la aplicación completa, con su servidor incluido. Eso es lo que se copia a un servidor, se mete en una imagen Docker o se sube a una plataforma cloud.

Ejecutar el JAR

java -jar target/ciclourbana-0.0.1-SNAPSHOT.jar

Arranca exactamente igual que con spring-boot:run. Comprueba que sigue respondiendo:

curl -s http://localhost:8080/api/v1/estaciones | jq '.[0].nombre'
# "Plaza Mayor"

Puedes pasar propiedades por la línea de comandos:

# Cambiar el puerto sin recompilar
java -jar target/ciclourbana-0.0.1-SNAPSHOT.jar --server.port=9090

# Limitar la memoria de la JVM (útil en contenedores)
java -Xmx256m -jar target/ciclourbana-0.0.1-SNAPSHOT.jar

Fíjate en la diferencia: -Xmx256m va antes de -jar porque es una opción de la JVM, mientras que --server.port=9090 va después porque es un argumento de la aplicación.

  1. Spring Boot DevTools y el reinicio automático

Al generar el proyecto añadimos spring-boot-devtools:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

Los atributos <optional>true</optional> y <scope>runtime</scope> hacen que DevTools no se propague a quien dependa de tu proyecto y que se desactive automáticamente cuando la aplicación se ejecuta desde un fat jar. Es decir: ayuda en desarrollo y desaparece en producción sin que hagas nada.

Qué aporta:

Función Efecto
Reinicio automático Al detectar clases recompiladas, reinicia el contexto de Spring en menos de un segundo
LiveReload Refresca el navegador automáticamente al cambiar recursos estáticos
Cachés desactivadas Desactiva las cachés de plantillas para ver los cambios al instante
Propiedades de desarrollo Aplica valores por defecto sensatos para desarrollo

Cómo funciona el reinicio rápido

DevTools usa dos cargadores de clases: uno base con las dependencias (Spring, Tomcat, Jackson), que no cambian nunca, y uno restart con tus clases, que sí cambian. Al reiniciar solo desecha y recarga el segundo. Por eso tarda décimas de segundo en lugar de los dos segundos del arranque completo.

Ese es también el motivo de que el hilo principal aparezca en el log como restartedMain en lugar de main.

Provocar un reinicio

El reinicio se dispara cuando cambian los ficheros .class en target/classes, no cuando guardas el .java. Por tanto hace falta una recompilación:

  • IntelliJ: Ctrl+F9 (Build Project), o activa Settings → Build → Compiler → Build project automatically.
  • Eclipse/STS: compila al guardar por defecto, así que basta con Ctrl+S.
  • VS Code: también compila al guardar.

Pruébalo. Con la aplicación en marcha mediante ./mvnw spring-boot:run, añade una quinta estación:

new Estacion(5L, "Mercado Viejo", "Calle del Mercado, 8", 20, 40.4211, -3.7101)

Recompila y observa en el log:

2026-08-31T10:31:08.442+02:00  INFO --- [  restartedMain] c.c.CicloUrbanaApplication : Started CicloUrbanaApplication in 0.612 seconds

Ahora curl devuelve cinco estaciones sin que hayas parado nada.

Si el reinicio te molesta, puedes desactivarlo sin quitar la dependencia:

# src/main/resources/application.properties
spring.devtools.restart.enabled=false

Errores Comunes y Consejos

  • Web server failed to start. Port 8080 was already in use. Tienes otra instancia arrancada, muy típico si olvidaste parar la del IDE antes de lanzar ./mvnw spring-boot:run. Localiza el proceso con lsof -i :8080 (Linux/macOS) o netstat -ano | findstr :8080 (Windows), o arranca en otro puerto con --server.port=9090.
  • El endpoint devuelve 404 aunque el código parece correcto. Casi siempre es porque el controlador está fuera del paquete raíz com.ciclourbana. El escaneo de componentes solo mira ese paquete y sus subpaquetes; lo veremos en la lección 01-04.
  • La aplicación arranca y termina inmediatamente. Falta spring-boot-starter-web. Sin dependencia web no hay servidor que mantenga vivo el proceso, así que el main acaba y la JVM se cierra.
  • DevTools no reinicia nada. Estás guardando el .java pero no se recompila. Activa la construcción automática en el IDE o compila a mano.
  • Ejecutar el .jar.original por error. Da no main manifest attribute. El fichero bueno es el .jar grande, sin sufijo.
  • Consejo — el JAR es autocontenido, no autónomo. Contiene todas las dependencias Java, pero sigue necesitando una JVM instalada donde se ejecute. Por eso las imágenes Docker de Spring Boot parten de una imagen con JDK o JRE (módulo 7).
  • Consejo — inicializa Git ya. El proyecto trae un .gitignore correcto. Con git init && git add . && git commit -m "Proyecto inicial de CicloUrbana" tendrás un punto de retorno seguro desde el primer día.

Ejercicios

Ejercicio 1

Genera el proyecto ciclourbana por línea de comandos con curl, ejecútalo y comprueba con curl que GET /api/v1/estaciones devuelve las cuatro estaciones de Ribalta. Anota el tiempo de arranque que aparece en el log.

Ejercicio 2

Añade al EstacionController un segundo endpoint GET /api/v1/estaciones/resumen que devuelva un objeto JSON con el número total de estaciones y la capacidad total sumada de la red. Usa un record ResumenRed.

Ejercicio 3

Empaqueta la aplicación, inspecciona el contenido del fat jar para localizar tus clases compiladas y el JAR de Tomcat embebido, y ejecútala en el puerto 9090 comprobando que responde.

Soluciones

Solución 1

curl https://start.spring.io/starter.zip \
  -d type=maven-project -d language=java \
  -d groupId=com.ciclourbana -d artifactId=ciclourbana \
  -d name=ciclourbana -d packageName=com.ciclourbana \
  -d packaging=jar -d javaVersion=21 \
  -d dependencies=web,devtools \
  -o ciclourbana.zip

unzip ciclourbana.zip -d ciclourbana
cd ciclourbana
chmod +x mvnw

Tras crear Estacion y EstacionController como se describe en la lección:

./mvnw spring-boot:run

En otra terminal:

curl -s http://localhost:8080/api/v1/estaciones | jq 'length'
# 4

curl -s http://localhost:8080/api/v1/estaciones | jq '.[].nombre'
# "Plaza Mayor"
# "Estación Norte"
# "Parque del Río"
# "Universidad"

El tiempo de arranque aparece en la línea Started CicloUrbanaApplication in X seconds. En una máquina normal está entre 1,5 y 3 segundos.

Solución 2

Primero el record del resumen, en el mismo paquete:

package com.ciclourbana.estaciones;

/**
 * Resumen agregado de la red de estaciones de Ribalta.
 */
public record ResumenRed(
        int totalEstaciones,
        int capacidadTotal
) {
}

Y el nuevo método en el controlador:

@GetMapping("/resumen")
public ResumenRed obtenerResumen() {
    int capacidadTotal = ESTACIONES.stream()
            .mapToInt(Estacion::capacidad)   // referencia al accesor del record
            .sum();
    return new ResumenRed(ESTACIONES.size(), capacidadTotal);
}

Explicación detallada:

  • @GetMapping("/resumen") se concatena con el @RequestMapping de la clase, produciendo GET /api/v1/estaciones/resumen.
  • Estacion::capacidad es una referencia a método. En un record, el accesor se llama igual que el componente (capacidad(), no getCapacidad()).
  • mapToInt(...).sum() convierte el flujo a un IntStream y suma los valores, evitando el autoboxing.
  • Devolver un objeto (no una lista) hace que Jackson genere un objeto JSON, no un array.

Comprobación:

curl -s http://localhost:8080/api/v1/estaciones/resumen | jq
# {
#   "totalEstaciones": 4,
#   "capacidadTotal": 108
# }

Solución 3

./mvnw clean package

# Localizar las clases propias dentro del fat jar
unzip -l target/ciclourbana-0.0.1-SNAPSHOT.jar | grep "com/ciclourbana"
#   BOOT-INF/classes/com/ciclourbana/CicloUrbanaApplication.class
#   BOOT-INF/classes/com/ciclourbana/estaciones/Estacion.class
#   BOOT-INF/classes/com/ciclourbana/estaciones/EstacionController.class

# Localizar Tomcat embebido
unzip -l target/ciclourbana-0.0.1-SNAPSHOT.jar | grep tomcat
#   BOOT-INF/lib/tomcat-embed-core-10.1.x.jar
#   BOOT-INF/lib/tomcat-embed-el-10.1.x.jar
#   BOOT-INF/lib/tomcat-embed-websocket-10.1.x.jar

# Ejecutar en el puerto 9090
java -jar target/ciclourbana-0.0.1-SNAPSHOT.jar --server.port=9090

Verificación en otra terminal:

curl -s http://localhost:9090/api/v1/estaciones | jq 'length'
# 4

Conclusión del ejercicio: tus clases viven en BOOT-INF/classes y las dependencias en BOOT-INF/lib como JAR anidados. Esa separación es exactamente la que permite a Spring Boot construir imágenes Docker por capas eficientes, algo que aprovecharemos en el módulo 7.

Conclusión

Ya tienes CicloUrbana en marcha. Has generado el proyecto con Spring Initializr desde la web y desde la terminal, has entendido las diez líneas de la clase de arranque, has creado el record Estacion y el EstacionController que expone GET /api/v1/estaciones con las cuatro estaciones de Ribalta, y lo has ejecutado con Maven, con el IDE y como fat jar autocontenido con java -jar. También sabes por qué ese JAR pesa 22 MB y cómo el JarLauncher carga los JAR anidados de BOOT-INF/lib.

En la siguiente lección, Entendiendo la Estructura del Proyecto, abriremos la caja: recorreremos el árbol de directorios generado, leeremos el pom.xml línea a línea, entenderemos qué es exactamente un starter y por qué el paquete raíz determina qué componentes encuentra Spring, y diseñaremos la organización de paquetes que CicloUrbana mantendrá durante todo el curso.

Curso de Spring Boot

Módulo 1: Introducción a Spring Boot

Módulo 2: Conceptos Básicos de Spring Boot

Módulo 3: Construyendo Servicios Web RESTful

Módulo 4: Acceso a Datos con Spring Boot

Módulo 5: Seguridad en Spring Boot

Módulo 6: Pruebas en Spring Boot

Módulo 7: Funciones Avanzadas de Spring Boot

Módulo 8: Despliegue de Aplicaciones Spring Boot

Módulo 9: Rendimiento y Monitoreo

Módulo 10: Mejores Prácticas y Consejos

© Copyright 2026. Todos los derechos reservados