Construye tu primera API REST con Java y Spring Boot

Un recorrido gratuito, práctico y verificable desde el entorno hasta un GET y un POST con validación.

Al finalizar tendrás una API local funcional, probada y comprendida. No se ejecuta código en Hostinger ni se promete empleo o certificación oficial.

  • 7lecciones guiadas
  • 5ejercicios
  • 12preguntas diagnósticas
  • 4 hduración estimada
Programadora probando una API REST con Java desde su ordenador
El código se ejecuta localmente con JDK y Maven; Hostinger solo sirve el campus PHP/MySQL.

Un resultado pequeño, completo y comprobable.

Al terminar tendrás una API local de tareas con consulta, creación, validación, errores y pruebas.

Versiones de esta edición

Java
25 LTS
Spring Boot
4.1.0
Maven
3.9.16 mediante Maven Wrapper only-script

Java 25 es una versión LTS vigente. Spring Boot 4.1.0 es una versión estable compatible con Java 25 y requiere al menos Java 17. Maven 3.9.16 es la versión estable recomendada al crear esta edición. Spring Boot administra las versiones transitivas; no se fijan versiones individuales sin necesidad.

Qué sabrás hacer

  1. Distinguir cliente, servidor, recurso, petición y respuesta.
  2. Preparar JDK 25 y verificar el proyecto desde terminal, sin depender del IDE.
  3. Explicar por qué GET y POST tienen semánticas distintas.
  4. Construir una API Spring Boot que liste y cree tareas en memoria.
  5. Validar JSON de entrada y devolver errores HTTP comprensibles.
  6. Ejecutar pruebas automatizadas y probar manualmente con curl o PowerShell.
  7. Identificar qué falta para convertir el ejercicio en un backend de producción.

Siete lecciones, del entorno a las pruebas

Trabaja en orden. Ejecuta cada comprobación en tu equipo y registra el primer error útil cuando algo falle.

01Preparación del entorno sin depender del IDE35 min

La aplicación se ejecutará exclusivamente en tu ordenador. Necesitas un JDK, no solo un runtime: el JDK incorpora el compilador y las herramientas que Maven utiliza. Este proyecto fija Java 25 y Spring Boot 4.1.0; el campus PHP nunca compila ni ejecuta el código que escribas.

Comprobación mínima

Instala una distribución OpenJDK 25 de un proveedor de confianza. Abre una terminal nueva y ejecuta java -version y javac -version. Ambas salidas deben comenzar por 25. Si no coinciden, revisa JAVA_HOME y el orden de PATH antes de cambiar el proyecto.

Wrapper reproducible

El repositorio incluye mvnw, mvnw.cmd y .mvn/wrapper/maven-wrapper.properties. Es la modalidad oficial only-script: en la primera ejecución descarga Maven 3.9.16 y posteriormente reutiliza esa distribución. En macOS o Linux usa ./mvnw -version; en Windows PowerShell usa .\mvnw.cmd -version.

Lectura de la estructura

src/main/java contiene código de producción, src/main/resources configuración, src/test/java pruebas y pom.xml el modelo de build. El IDE importa el POM, pero la terminal es la referencia reproducible.

Practica ahora

  1. Descomprime starter.zip en una ruta sin caracteres extraños.
  2. Ejecuta las tres comprobaciones de versión.
  3. Ejecuta el test inicial y guarda la salida.
  4. Anota sistema operativo, proveedor del JDK y cualquier corrección realizada.

Verificación: La orden del Wrapper muestra Maven 3.9.16 y Java 25; el test de contexto termina con BUILD SUCCESS.

Errores frecuentes
  • Instalar solo un JRE
  • Abrir el IDE antes de corregir PATH
  • Ejecutar mvn global en vez del Wrapper
  • Guardar el proyecto dentro de un ZIP sin extraer
02HTTP y REST: el contrato antes del código35 min

HTTP es el protocolo de intercambio. REST es un estilo para organizar recursos y aprovechar la semántica de HTTP; no significa que cualquier JSON sobre una URL sea REST.

Anatomía de una interacción

Una petición contiene método, URI, cabeceras y, a veces, cuerpo. Una respuesta contiene un estado, cabeceras y una representación. En GET /api/v1/tareas, el cliente pide una colección. En POST /api/v1/tareas, solicita crear un elemento a partir del JSON enviado.

Semántica que importa

GET es seguro: no pretende modificar estado. También es idempotente: repetirlo mantiene el mismo efecto, aunque los datos puedan cambiar entre lecturas. POST no es idempotente por definición; dos envíos pueden crear dos recursos. Una creación correcta devuelve 201 y una cabecera Location. Un JSON inválido pertenece al grupo 400; una ruta inexistente no debe disfrazarse como 200.

Contrato del ejercicio

GET /api/v1/tareas          -> 200 + lista JSON
GET /api/v1/tareas/{id}     -> 200 o 404
POST /api/v1/tareas         -> 201 + tarea + Location
POST con título vacío       -> 400 + problem+json

La versión v1 forma parte de la ruta para hacer visible el contrato. No resuelve por sí sola una estrategia de evolución, pero evita cambiar silenciosamente a los consumidores.

Practica ahora

  1. Dibuja cliente, API y almacén en memoria.
  2. Escribe método, ruta, estado y cuerpo de las cuatro interacciones.
  3. Explica en una frase por qué POST repetido puede duplicar una tarea.

Verificación: Puedes predecir el estado esperado sin mirar el controller.

Errores frecuentes
  • Nombrar rutas con verbos
  • Responder siempre 200
  • Confundir JSON con REST
  • Afirmar que POST es idempotente
03Creación guiada del proyecto Spring Boot35 min

El proyecto ya contiene una base para evitar depender de un generador web. El padre spring-boot-starter-parent:4.1.0 gestiona versiones compatibles. spring-boot-starter-webmvc incorpora Spring MVC y el servidor; spring-boot-starter-validation incorpora Jakarta Validation.

Clase principal y paquetes

PrimeraApiApplication está en el paquete raíz com.kintavor.primeraapi. @SpringBootApplication activa configuración, autoconfiguración y escaneo descendente. Si mueves la clase fuera del paquete raíz, Spring puede dejar de descubrir controllers y servicios.

Primer arranque

Ejecuta ./mvnw spring-boot:run o .\mvnw.cmd spring-boot:run. Busca el puerto 8080 y un arranque sin excepciones. Detén con Ctrl+C; no cierres la terminal a la fuerza si puedes permitir un cierre ordenado.

Qué hace la autoconfiguración

Boot observa clases y configuración disponibles y crea infraestructura razonable. No genera tus reglas de negocio. Si el puerto está ocupado, utiliza temporalmente --spring-boot.run.arguments=--server.port=8081; no mates procesos desconocidos.

Ejecuta ./mvnw test antes y después de cada paso. Una compilación verde no demuestra que el contrato HTTP sea correcto, pero elimina una categoría de fallos.

Practica ahora

  1. Abre pom.xml y localiza las tres versiones centralizadas.
  2. Arranca la aplicación y registra el puerto.
  3. Detén la aplicación y ejecuta la prueba de contexto.
  4. Haz un commit local llamado 'Prepara el esqueleto de la API'.

Verificación: El contexto arranca y el alumno puede explicar de dónde procede cada dependencia.

Errores frecuentes
  • Fijar versiones de Spring transitivas
  • Situar paquetes fuera del escaneo
  • Cambiar varias dependencias a la vez
  • Confundir arrancar con probar
04Primer endpoint GET y representación JSON35 min

El controller traduce HTTP; no debe convertirse en almacén ni en regla de negocio. TareaService conserva temporalmente las tareas en memoria y TareaResponse define qué se publica.

Ruta y respuesta

@GetMapping
List<TareaResponse> listar() {
  return service.listar().stream().map(TareaResponse::from).toList();
}

La anotación de clase aporta /api/v1/tareas. Como la operación termina normalmente, Spring serializa la lista y responde 200 con JSON. Un record es apropiado para un DTO pequeño porque declara sus componentes sin setters.

Separar modelos

Tarea representa el dato interno. TareaResponse representa el contrato exterior. Aunque ahora tengan campos parecidos, separarlos evita que un cambio interno publique accidentalmente información nueva.

Prueba manual

Con la aplicación arrancada ejecuta curl -i http://localhost:8080/api/v1/tareas. Comprueba el estado, Content-Type y que la respuesta sea un array. En Windows puedes usar Invoke-RestMethod. Guarda la petición exacta en tu cuaderno: una captura sin orden no es suficiente para reproducir el resultado.

Practica ahora

  1. Implementa o compara el GET de colección.
  2. Añade una segunda tarea de ejemplo solo durante una prueba.
  3. Predice el orden antes de ejecutar y explica por qué se ordena por id.
  4. Activa el caso GET de la prueba de aceptación.

Verificación: GET devuelve 200, JSON y una lista ordenada sin exponer el mapa interno.

Errores frecuentes
  • Devolver el Map directamente
  • Crear el servicio con new dentro del controller
  • Modificar datos desde GET
  • Afirmar solo el tamaño y no el contenido
05Endpoint POST: crear un recurso y comunicarlo35 min

POST recibe una intención de creación. @RequestBody convierte el JSON a CrearTareaRequest; el servicio asigna identidad y el controller devuelve la representación creada.

Implementación

@PostMapping
ResponseEntity<TareaResponse> crear(
    @Valid @RequestBody CrearTareaRequest request,
    UriComponentsBuilder uris) {
  Tarea creada = service.crear(request.titulo(), request.descripcion());
  URI location = uris.path("/api/v1/tareas/{id}")
      .buildAndExpand(creada.id()).toUri();
  return ResponseEntity.created(location).body(TareaResponse.from(creada));
}

El identificador se asigna en servidor; aceptar un id arbitrario del cliente abriría colisiones y reglas ambiguas. La cabecera Location señala el recurso creado y el cuerpo evita obligar al cliente a realizar otra petición para conocerlo.

Prueba manual

curl -i -X POST http://localhost:8080/api/v1/tareas \
  -H 'Content-Type: application/json' \
  -d '{"titulo":"Leer sobre HTTP","descripcion":"Anotar estados"}'

Repite el envío conscientemente: se crean dos recursos porque no hemos diseñado idempotencia. Esto no es un error del framework, sino una propiedad que un caso de negocio real debe decidir.

Practica ahora

  1. Implementa el POST.
  2. Comprueba 201 y Location.
  3. Usa GET por id con el identificador recibido.
  4. Envía dos veces y registra la diferencia.

Verificación: La respuesta tiene 201, cuerpo coherente e identificador generado por servidor.

Errores frecuentes
  • Devolver 200
  • Aceptar id del cliente
  • Construir Location con concatenación insegura
  • Guardar el DTO como modelo interno
06Validación y errores HTTP que enseñan sin filtrar datos35 min

Validar evita que datos imposibles entren en la aplicación. El DTO declara forma y tamaño; el servicio normaliza; un manejador global convierte excepciones conocidas en respuestas uniformes.

Restricciones de entrada

public record CrearTareaRequest(
  @NotBlank @Size(max = 80) String titulo,
  @Size(max = 300) String descripcion
) {}

@Valid activa estas restricciones al recibir el cuerpo. Un título compuesto solo por espacios falla. El límite de tamaño protege contrato y recursos, aunque una API real también limitaría el cuerpo total en el servidor.

Problem Details

@RestControllerAdvice transforma MethodArgumentNotValidException en estado 400 y añade errores por campo. La respuesta exterior no contiene stack trace. TareaNoEncontradaException se traduce a 404: la petición era válida, pero el recurso no existe.

Dos niveles

Jakarta Validation comprueba forma en la frontera. Las invariantes que deben cumplirse aun sin HTTP pertenecen al dominio o servicio. Copiar la misma regla en todos los controllers produce inconsistencias.

Prueba un título vacío, uno de 81 caracteres y un id inexistente. Los tres fallos deben ser previsibles y no deben detener el proceso.

Practica ahora

  1. Añade las restricciones del request.
  2. Implementa o compara el advice.
  3. Activa la prueba de validación.
  4. Comprueba manualmente un 404 y un 400.

Verificación: Entradas inválidas no llegan al servicio y cada fallo devuelve estado y formato correctos.

Errores frecuentes
  • Olvidar @Valid
  • Devolver stack trace
  • Usar 500 para datos inválidos
  • Confiar solo en validación del frontend
07Prueba de la API y siguiente paso profesional30 min

Una comprobación manual ayuda a explorar; una prueba automatizada conserva el contrato. La solución usa MockMvc para atravesar routing, JSON, validación y controller sin abrir un puerto, y una prueba unitaria para el servicio en memoria.

Ciclo de verificación

  1. Predice el resultado y ejecuta ./mvnw test.
  2. Lee el primer fallo, no la última línea.
  3. Reduce el cambio y vuelve a ejecutar una prueba concreta.
  4. Ejecuta la suite completa antes de cerrar.

Una aserción útil comprueba el estado y datos importantes. Comprobar únicamente que existe una respuesta permitiría errores silenciosos. La prueba de validación demuestra también que el servicio no acepta un cuerpo sin título.

Lo que esta demo no pretende resolver

Los datos desaparecen al reiniciar. No hay PostgreSQL, autenticación, roles, transacciones, paginación, OpenAPI ni despliegue. Esta limitación es deliberada: primero se comprende el flujo HTTP completo y después se sustituye el almacén sin deformar el contrato.

Cierre

Completa los cinco ejercicios, realiza el diagnóstico y compara tu versión con solution.zip solo después de registrar tus decisiones. La solución es una referencia, no la única implementación válida.

Practica ahora

  1. Ejecuta todas las pruebas.
  2. Provoca un fallo cambiando temporalmente un estado esperado.
  3. Restaura el código y explica la primera línea útil del diagnóstico.
  4. Escribe tres riesgos para una versión con usuarios reales.

Verificación: La suite termina en verde y el alumno explica qué cubre y qué no cubre.

Errores frecuentes
  • Cambiar la prueba para que acepte el bug
  • Depender del orden
  • Compartir datos mutables
  • Confundir suite verde con producción segura

Dos ZIP separados para trabajar con intención.

Empieza por el proyecto inicial. Consulta la solución solo después de ejecutar pruebas, registrar tus decisiones y utilizar las pistas.

Proyecto inicial

Base Maven compilable, instrucciones, casos de aceptación y colección de peticiones.

Descargar starter.zip
Resultado de referencia

GET, POST, validación, Problem Details y pruebas completas para comparar decisiones.

Descargar solution.zip
Seguridad: KINTAVOR no recibe ni ejecuta tu proyecto. No subas claves, tokens ni datos reales al repositorio.

Cinco ejercicios con pistas progresivas

Lee el enunciado, produce evidencia y abre las pistas de una en una. La solución queda al final de cada ejercicio.

  1. 01

    Diagnostica el entorno

    Ejecuta java -version, javac -version y el Maven Wrapper. Construye una tabla con versión observada, versión requerida y acción si no coincide. Simula por escrito el caso en que java sea 25 y javac sea 21.

    Evidencia: Tabla y salida sin rutas personales sensibles.

    Abrir pistas
    1. Java y javac pueden proceder de instalaciones distintas.
    2. Consulta JAVA_HOME y el orden de PATH.
    3. La corrección debe verificarse en una terminal nueva, no solo en el IDE.
    Comparar con la solución razonada

    Las tres herramientas deben identificar Java 25 y Maven debe ser 3.9.16. Si java y javac difieren, localiza ambas rutas, apunta JAVA_HOME al JDK 25 y antepone su carpeta bin en PATH. Cierra y abre la terminal, repite la comprobación y no cambies el POM para esconder el problema.

  2. 02

    Añade un filtro completadas

    Amplía GET /api/v1/tareas con un parámetro opcional completada. Sin parámetro devuelve todas; con true o false filtra. No dupliques rutas ni modifiques estado.

    Evidencia: Diff, tres pruebas y ejemplos curl.

    Abrir pistas
    1. Usa @RequestParam(required=false).
    2. El servicio puede aceptar Boolean para distinguir ausencia.
    3. Añade tres pruebas: ausencia, true y false.
    Comparar con la solución razonada

    El controller recibe Boolean completada y delega. El servicio retorna la lista ordenada y aplica filter solo si completada no es null. GET permanece seguro; las pruebas crean datos controlados o comprueban presencia sin depender de ids globales.

  3. 03

    Corrige un POST que miente

    Revisa una versión que responde 200, acepta id en el JSON y no incluye Location. Enumera los incumplimientos, corrige el contrato y crea una prueba que habría detectado cada uno.

    Evidencia: Lista de incumplimientos y prueba antes/después.

    Abrir pistas
    1. La creación tiene un estado específico.
    2. La identidad pertenece al servidor en este diseño.
    3. Location puede comprobarse con una expresión regular o prefix.
    Comparar con la solución razonada

    El request elimina id; el servicio lo genera. El controller usa ResponseEntity.created(location), por lo que responde 201 y Location. Las pruebas rechazan o ignoran campos no admitidos según la política explícita, afirman isCreated y verifican /api/v1/tareas/{id}.

  4. 04

    Diseña validaciones observables

    Crea una tabla de particiones para título y descripción. Implementa pruebas para vacío, espacios, límite exacto y un carácter por encima. Comprueba el campo problemático sin depender del texto completo del mensaje.

    Evidencia: Tabla de particiones y suite verde.

    Abrir pistas
    1. Los límites son 80 y 300.
    2. @NotBlank distingue espacios de contenido.
    3. Afirmar la clave del mapa de errores es más estable que el texto localizado.
    Comparar con la solución razonada

    Las particiones mínimas incluyen null/vacío/espacios, longitudes 1, 80 y 81 para título; ausencia, 300 y 301 para descripción. Los casos válidos esperan 201 y los inválidos 400 con errores.titulo o errores.descripcion.

  5. 05

    Planifica el salto a PostgreSQL

    Sin implementar dependencias nuevas, diseña cómo sustituir el mapa por PostgreSQL. Incluye tabla, restricciones, interfaz de repositorio, límite transaccional, migración y pruebas. Mantén el contrato HTTP salvo una razón documentada.

    Evidencia: Diagrama, DDL, interfaz y estrategia de pruebas.

    Abrir pistas
    1. Empieza por el modelo de datos, no por anotaciones.
    2. El servicio no debería conocer SQL.
    3. Una integración debe probar contra semántica PostgreSQL o declarar la aproximación.
    Comparar con la solución razonada

    Propón tabla tarea(id identity PK, titulo varchar(80) not null, descripcion varchar(300), completada boolean not null). Define un puerto Tareas con listar, buscar y guardar; una implementación JPA o JDBC queda en infraestructura. La creación es una transacción corta. Flyway versiona el esquema. Mantén rutas y DTO, añade pruebas unitarias del caso de uso y una integración aislada con PostgreSQL local opcional.

Diagnóstico: ¿entiendes el flujo de tu primera API?

Selecciona una respuesta por pregunta antes de consultar la explicación. El test orienta el estudio; no es una certificación oficial.

1. ¿Qué comprobación demuestra que hay un compilador Java 25 disponible?
2. ¿Qué ventaja concreta aporta ./mvnw frente a mvn?
3. ¿Cuál es la responsabilidad principal de GET /api/v1/tareas?
4. Una tarea se crea correctamente. ¿Qué respuesta comunica mejor el resultado?
5. ¿Por qué el cliente no envía el id en CrearTareaRequest?
6. ¿Qué combinación rechaza un título vacío o compuesto solo por espacios y limita su tamaño?
7. ¿Dónde debe aplicarse @Valid para validar el DTO recibido por el controller?
8. ¿Qué diferencia correcta existe entre 400 y 404 en esta API?
9. ¿Por qué se utiliza TareaResponse en vez de devolver el Map interno?
10. Un test de POST comprueba solo que no hubo excepción. ¿Qué falta como mínimo?
11. ¿Qué ocurre con las tareas del ejemplo al reiniciar la aplicación?
12. ¿Qué afirmación describe mejor el alcance del mini curso?

Tu siguiente paso: persistencia, seguridad y arquitectura.

Continúa en KINTAVOR Java Backend Profesional para transformar esta primera vertical en aplicaciones con Java, SQL, PostgreSQL, JPA, seguridad, testing integral, Docker local opcional, CI, cloud y seis proyectos de portfolio.

KINTAVOR ofrece formación privada. El certificado completo es propio y no oficial. No existe afiliación con Oracle, OpenJDK, Spring, VMware o Broadcom; las marcas se usan de forma descriptiva.

Ver el curso completoSolicitar una prueba del campus