SpringBoot
Tutorial SpringBoot: Controladores Spring REST
Spring Boot controladores REST: creación y uso. Domina la creación y uso de controladores REST en Spring Boot con ejemplos prácticos y detallados.
Aprende SpringBoot GRATIS y certifícateQué es REST y qué diferencias hay con MVC, SOAP, GraphQL, gRPC
- REST:
- REST (Representational State Transfer) es un estilo arquitectónico para diseñar servicios web que se basa en recursos y en operaciones estándar del protocolo HTTP. En REST, cada recurso es identificado por una URI y se manipula a través de métodos HTTP como GET, POST, PUT y DELETE. Este enfoque promueve la interoperabilidad y la escalabilidad al utilizar estándares web ampliamente adoptados.
- MVC:
- En contraste, MVC (Modelo-Vista-Controlador) es un patrón de diseño de software que divide una aplicación en tres componentes interconectados: el modelo, la vista y el controlador. Mientras que REST se centra en la comunicación entre sistemas mediante servicios web, MVC se enfoca en la organización interna de una aplicación para mejorar su mantenibilidad y modularidad. En el contexto de Spring Boot, los controladores MVC suelen devolver vistas (como páginas HTML), mientras que los controladores REST devuelven datos en formatos como JSON o XML.
- SOAP:
- SOAP (Simple Object Access Protocol) es un protocolo para el intercambio de información estructurada en la implementación de servicios web. A diferencia de REST, que es más flexible y ligero, SOAP es más estricto y utiliza XML para definir el mensaje y la estructura de los datos. SOAP incorpora características como extensiones para seguridad y transacciones, lo que añade complejidad al protocolo. Por ello, SOAP es más común en entornos empresariales donde se requieren mecanismos más robustos y estandarizados.
- GraphQL
- GraphQL es un lenguaje de consulta para APIs desarrollado por Facebook que permite a los clientes definir con precisión qué datos necesitan. Esto reduce el número de peticiones y evita el overfetching y underfetching de datos. A diferencia de REST, donde los endpoints están asociados a recursos específicos, en GraphQL existe un único endpoint que interpreta y ejecuta las consultas. Aunque ofrece mayor flexibilidad, también introduce complejidad en la implementación y puede requerir un mayor control sobre el rendimiento y la seguridad.
- gRPC
- gRPC es un framework de comunicación remota desarrollado por Google que utiliza el protocolo HTTP/2 y la serialización Protocol Buffers para mejorar el rendimiento y la eficiencia. gRPC permite la comunicación de alta velocidad entre servicios en arquitecturas de microservicios, soportando tipos de comunicación como streaming bidireccional. A diferencia de REST, que suele ser textual y utiliza JSON, gRPC es binario y orientado a contratos, lo que reduce la latencia y el consumo de ancho de banda.
Creación de un Controlador REST con @RestController
En Spring Boot 3, un Controlador REST es una clase que maneja solicitudes HTTP y produce respuestas en formatos como JSON o XML. Para definir un controlador REST, se utiliza la anotación @RestController
, que es una especialización de @Controller
y @ResponseBody
. Esta anotación indica a Spring que los métodos de la clase producirán respuestas directamente en el cuerpo de la respuesta HTTP, en lugar de renderizar vistas.
A continuación, se muestra un ejemplo básico de un controlador REST:
package com.ejemplo.demo.controlador;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
@RestController
public class SaludoControlador {
@GetMapping("/saludo")
public String saludar() {
return "¡Hola, mundo!";
}
}
En este ejemplo, la clase SaludoControlador
está anotada con @RestController
, lo que la convierte en un componente gestionado por Spring. El método saludar()
está anotado con @GetMapping("/saludo")
, lo que significa que manejará las solicitudes HTTP GET dirigidas al endpoint /saludo
. Al ser un controlador REST, el método devuelve directamente una cadena que se envía en el cuerpo de la respuesta.
Es importante destacar que las anotaciones como @GetMapping
son atajos para @RequestMapping(method = RequestMethod.GET)
. Estas anotaciones específicas de método ayudan a crear código más legible y conciso. Spring Boot 3 soporta varias de estas anotaciones, como @PostMapping
, @PutMapping
, @DeleteMapping
y @PatchMapping
, que corresponden a los métodos HTTP estándar.
Para manejar parámetros en las solicitudes, se pueden utilizar anotaciones como @PathVariable
y @RequestParam
. Por ejemplo:
@GetMapping("/saludo/{nombre}")
public String saludarConNombre(@PathVariable String nombre) {
return "¡Hola, " + nombre + "!";
}
En este caso, {nombre}
en la ruta indica un parámetro de ruta, y @PathVariable
extrae ese valor para usarlo en el método. Si se requieren parámetros de consulta, se puede usar @RequestParam
:
@GetMapping("/saludo")
public String saludarConIdioma(@RequestParam String nombre, @RequestParam String idioma) {
if ("es".equals(idioma)) {
return "¡Hola, " + nombre + "!";
} else if ("en".equals(idioma)) {
return "Hello, " + nombre + "!";
} else {
return "Idioma no soportado.";
}
}
Los controladores REST pueden devolver objetos complejos que Spring automáticamente serializará a JSON o XML, dependiendo de la configuración y las cabeceras de la solicitud. Por ejemplo:
@GetMapping("/usuario/{id}")
public Usuario obtenerUsuario(@PathVariable Long id) {
return servicioUsuario.obtenerPorId(id);
}
Aquí, Usuario
es una clase de modelo, y Spring Boot se encarga de convertir la instancia en una representación JSON en el cuerpo de la respuesta. Es esencial que la clase Usuario
tenga los métodos getter y setter adecuados, y que esté gestionada por el sistema de serialización de Jackson incluido en Spring Boot.
Para manejar solicitudes POST, se utiliza @PostMapping
y se puede consumir datos del cuerpo de la solicitud con @RequestBody
:
@PostMapping("/usuario")
public Usuario crearUsuario(@RequestBody Usuario nuevoUsuario) {
return servicioUsuario.guardar(nuevoUsuario);
}
En este ejemplo, el controlador recibe un objeto Usuario
en formato JSON en el cuerpo de la solicitud y lo deserializa automáticamente. Luego, se procesa y se devuelve el usuario creado, que se serializa nuevamente a JSON en la respuesta.
Es recomendable manejar adecuadamente las excepciones y los códigos de estado HTTP. Por defecto, Spring Boot devuelve 200 OK para respuestas exitosas. Sin embargo, se pueden especificar otros códigos de estado utilizando la clase ResponseEntity
:
@GetMapping("/usuario/{id}")
public ResponseEntity<Usuario> obtenerUsuario(@PathVariable Long id) {
Usuario usuario = servicioUsuario.obtenerPorId(id);
if (usuario != null) {
return ResponseEntity.ok(usuario);
} else {
return ResponseEntity.notFound().build();
}
}
En este caso, si el usuario existe, se devuelve una respuesta con 200 OK y el usuario en el cuerpo. Si no existe, se devuelve una respuesta con 404 Not Found. El uso de ResponseEntity
proporciona un mayor control sobre la respuesta HTTP, incluyendo las cabeceras y los códigos de estado.
Para resumir, la creación de un controlador REST en Spring Boot 3 implica:
- Anotar la clase con
@RestController
para definirla como un controlador REST. - Utilizar las anotaciones de mapeo como
@GetMapping
,@PostMapping
, etc., para asociar métodos a rutas y métodos HTTP específicos. - Manejar parámetros de ruta y de consulta con
@PathVariable
y@RequestParam
. - Utilizar
@RequestBody
para consumir datos del cuerpo de la solicitud en métodos que manejan solicitudes POST o PUT. - Devolver objetos que serán serializados automáticamente a JSON o XML.
- Utilizar
ResponseEntity
cuando se requiere un mayor control sobre la respuesta HTTP.
Seguir estas prácticas garantiza que los controladores REST sean eficientes y estén alineados con los estándares de desarrollo modernos en Spring Boot 3.
Qué es ResponseEntity y ResponseStatusException
En el desarrollo de APIs REST con Spring Boot 3, es fundamental gestionar adecuadamente las respuestas HTTP que se envían al cliente. Aquí es donde entran en juego ResponseEntity y ResponseStatusException, dos herramientas esenciales para controlar tanto el contenido como el estado de las respuestas.
ResponseEntity es una clase genérica de Spring Framework que representa toda la respuesta HTTP, incluyendo el código de estado, las cabeceras y el cuerpo. Al utilizar ResponseEntity, el desarrollador tiene un control completo sobre lo que se devuelve al cliente, lo que permite adaptar la respuesta a las necesidades específicas de cada situación.
Por ejemplo, para devolver una respuesta con un código de estado 200 OK y un cuerpo que contiene un objeto de tipo Usuario
, se puede hacer lo siguiente:
@GetMapping("/usuarios/{id}")
public ResponseEntity<Usuario> obtenerUsuario(@PathVariable Long id) {
var usuario = servicioUsuario.buscarPorId(id);
if (usuario.isPresent()) {
return ResponseEntity.ok(usuario.get());
} else {
return ResponseEntity.notFound().build();
}
}
En este ejemplo, se utiliza el método estático ok()
de ResponseEntity para construir una respuesta exitosa. Si el usuario no existe, se devuelve una respuesta con el estado 404 Not Found utilizando notFound().build()
.
Además de los métodos estáticos como ok()
, created()
, accepted()
, ResponseEntity permite agregar cabeceras adicionales a la respuesta:
@PostMapping("/usuarios")
public ResponseEntity<Usuario> crearUsuario(@RequestBody Usuario nuevoUsuario) {
var usuarioCreado = servicioUsuario.guardar(nuevoUsuario);
return ResponseEntity
.created(URI.create("/usuarios/" + usuarioCreado.getId()))
.header("Custom-Header", "ValorPersonalizado")
.body(usuarioCreado);
}
Aquí, created() se utiliza para indicar que se ha creado un nuevo recurso, estableciendo la cabecera Location con la URI del recurso creado. También se añade una cabecera personalizada Custom-Header para ilustrar cómo se pueden incluir cabeceras adicionales.
Por otro lado, ResponseStatusException es una excepción que permite indicar un código de estado HTTP específico cuando ocurre un error en el procesamiento de una solicitud. Lanzar una ResponseStatusException causa que Spring devuelva automáticamente una respuesta con el código de estado especificado y, opcionalmente, un mensaje de error.
Veamos un ejemplo:
@GetMapping("/productos/{id}")
public Producto obtenerProducto(@PathVariable Long id) {
return servicioProducto.buscarPorId(id)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "Producto no encontrado"));
}
En este caso, si el producto no se encuentra, se lanza una ResponseStatusException con el estado 404 Not Found y un mensaje descriptivo. Spring se encarga de construir la respuesta con el código de estado y el cuerpo adecuados.
Es posible personalizar aún más las excepciones utilizando subclases específicas de ResponseStatusException, como HttpClientErrorException o HttpServerErrorException, aunque en la mayoría de los casos la clase base es suficiente.
Otra forma de controlar el código de estado en las excepciones es mediante la anotación @ResponseStatus en las clases de excepción personalizadas:
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class DatosInvalidosException extends RuntimeException {
public DatosInvalidosException(String mensaje) {
super(mensaje);
}
}
Al lanzar esta excepción, Spring devolverá una respuesta con el estado 400 Bad Request y el mensaje proporcionado.
Integrando ResponseEntity y ResponseStatusException en nuestros controladores, podemos gestionar de manera eficaz las diversas situaciones que pueden ocurrir durante el procesamiento de solicitudes, proporcionando respuestas coherentes y significativas a los clientes de nuestra API.
Por ejemplo, al actualizar un recurso:
@PutMapping("/usuarios/{id}")
public ResponseEntity<Usuario> actualizarUsuario(@PathVariable Long id, @RequestBody Usuario usuarioActualizado) {
if (!id.equals(usuarioActualizado.getId())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "El ID del usuario no coincide");
}
var usuario = servicioUsuario.actualizar(usuarioActualizado);
return ResponseEntity.ok(usuario);
}
Si el ID proporcionado en la URL no coincide con el del cuerpo de la solicitud, se lanza una ResponseStatusException con el estado 400 Bad Request.
Finalmente, es importante destacar que el uso de ResponseEntity y ResponseStatusException contribuye a la creación de APIs más robustas y expresivas. Al tener un control preciso sobre las respuestas, se mejora la comunicación con los clientes y se facilita el manejo de errores, lo que es esencial en aplicaciones modernas basadas en servicios RESTful.
Métodos GET sin y con parámetros
En los Controladores REST de Spring Boot 3, los métodos GET se utilizan para recuperar información del servidor. Para definir un método que maneje solicitudes GET, se emplea la anotación @GetMapping
, que se coloca sobre el método correspondiente y especifica la ruta de acceso.
Por ejemplo, para manejar una solicitud GET sin parámetros:
@GetMapping("/productos")
public List<Producto> obtenerTodosLosProductos() {
return servicioProducto.listarProductos();
}
En este caso, el método obtenerTodosLosProductos
responderá a las solicitudes dirigidas a /productos
, devolviendo una lista de objetos Producto.
Cuando es necesario manejar parámetros en las solicitudes GET, se pueden utilizar variables de ruta o parámetros de consulta. Las variables de ruta forman parte de la URL y se definen entre llaves { }
, mientras que los parámetros de consulta se añaden después de un signo de interrogación ?
en la URL.
Para emplear variables de ruta, se utiliza la anotación @PathVariable
:
@GetMapping("/productos/{id}")
public Producto obtenerProductoPorId(@PathVariable Long id) {
return servicioProducto.buscarPorId(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Producto no encontrado"));
}
En este ejemplo, {id}
indica que se espera un valor variable en la ruta, y @PathVariable
asocia ese valor al parámetro del método id
. Es esencial que el tipo del parámetro coincida con el esperado en la ruta.
Si se desean manejar parámetros de consulta, se emplea la anotación @RequestParam
:
@GetMapping("/productos")
public List<Producto> buscarProductosPorCategoria(@RequestParam String categoria) {
return servicioProducto.buscarPorCategoria(categoria);
}
Aquí, el parámetro categoria
se extrae de la URL de la solicitud, como en /productos?categoria=Electrónica
. La anotación @RequestParam
enlaza el valor proporcionado con el parámetro del método.
Los parámetros opcionales se pueden definir estableciendo el atributo required
a false
y, opcionalmente, asignando un valor por defecto con defaultValue
:
@GetMapping("/productos")
public List<Producto> buscarProductos(
@RequestParam(required = false) String categoria,
@RequestParam(defaultValue = "0") int pagina) {
return servicioProducto.buscarProductos(categoria, pagina);
}
De este modo, si no se proporciona categoria
, su valor será null
, y si no se especifica pagina
, tomará el valor por defecto 0
.
Es posible combinar variables de ruta y parámetros de consulta en un mismo método:
@GetMapping("/productos/categoria/{categoria}")
public List<Producto> buscarProductosPorCategoriaYPrecio(
@PathVariable String categoria,
@RequestParam(required = false) Double precioMin,
@RequestParam(required = false) Double precioMax) {
return servicioProducto.buscarPorCategoriaYPrecio(categoria, precioMin, precioMax);
}
En este caso, se busca una lista de productos en una categoría específica, con la opción de filtrar por un rango de precios.
Cuando se requiere manejar un número variable de parámetros o desconocidos, se puede utilizar un Map para capturarlos:
@GetMapping("/productos/filtrar")
public List<Producto> filtrarProductos(@RequestParam Map<String, String> parametros) {
return servicioProducto.filtrarConParametros(parametros);
}
Aquí, todos los parámetros de consulta proporcionados en la solicitud se recopilan en un mapa y se pasan al servicio para su procesamiento.
También es posible acceder a los datos de la solicitud más allá de los parámetros habituales. Por ejemplo, para obtener información de las cabeceras HTTP, se utiliza @RequestHeader
:
@GetMapping("/productos")
public List<Producto> obtenerProductosConIdioma(@RequestHeader("Accept-Language") String idioma) {
return servicioProducto.listarProductosEnIdioma(idioma);
}
Este método permite adaptar la respuesta en función del idioma preferido del cliente, obtenido de la cabecera Accept-Language
.
Para manejar solicitudes GET que requieren condiciones específicas, se pueden utilizar los atributos params
, headers
y produces
en la anotación @GetMapping
:
@GetMapping(value = "/productos", params = "destacados=true")
public List<Producto> obtenerProductosDestacados() {
return servicioProducto.listarDestacados();
}
En este ejemplo, el método solo responderá a solicitudes que incluyan el parámetro destacados=true
.
Es importante manejar correctamente las posibles excepciones que pueden surgir al procesar los parámetros. Por ejemplo, si se espera un parámetro numérico y el cliente proporciona un valor no válido, Spring generará una excepción. Para proporcionar respuestas más amigables, se pueden utilizar controladores de excepciones o validar los parámetros antes de procesarlos.
Además, con el uso de expresiones regulares, se puede restringir el formato de las variables de ruta:
@GetMapping("/productos/{codigo:[A-Z]{3}\\d{4}}")
public Producto obtenerProductoPorCodigo(@PathVariable String codigo) {
return servicioProducto.buscarPorCodigo(codigo)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Producto no encontrado"));
}
Aquí, la variable codigo
debe cumplir con el patrón especificado, asegurando que solo se procesen códigos válidos.
En resumen, los métodos GET en Spring Boot 3 ofrecen una gran flexibilidad para manejar solicitudes con o sin parámetros. A través de las anotaciones @GetMapping
, @PathVariable
y @RequestParam
, es posible construir rutas y métodos que respondan eficientemente a las necesidades de la aplicación, garantizando una API REST sólida y escalable.
Métodos POST, PUT y PATCH
En el desarrollo de APIs REST con Spring Boot 3, los métodos POST, PUT y PATCH son fundamentales para manejar la creación y actualización de recursos. Estos métodos HTTP permiten enviar datos en el cuerpo de la solicitud, los cuales el servidor procesa para modificar el estado de la aplicación.
Para manejar solicitudes POST, que se utilizan comúnmente para crear nuevos recursos, se emplea la anotación @PostMapping
. Esta se coloca sobre un método en el controlador y especifica la ruta asociada al endpoint. El método suele recibir un objeto que representa el nuevo recurso, deserializado automáticamente desde el cuerpo de la solicitud gracias a la anotación @RequestBody
.
@PostMapping("/usuarios")
public ResponseEntity<Usuario> crearUsuario(@RequestBody Usuario usuario) {
Usuario nuevoUsuario = servicioUsuario.guardar(usuario);
return ResponseEntity.status(HttpStatus.CREATED).body(nuevoUsuario);
}
En este ejemplo, el método crearUsuario
recibe un objeto Usuario
mediante @RequestBody
, indicando que los datos se enviarán en formato JSON. El servicio servicioUsuario.guardar(usuario)
persiste el nuevo usuario, y se devuelve una respuesta con el código de estado 201 Created utilizando ResponseEntity
.
Los métodos PUT se emplean para reemplazar completamente un recurso existente. En Spring Boot 3, se utiliza la anotación @PutMapping
para manejar estas solicitudes. Al igual que con POST, se recibe el objeto en el cuerpo de la solicitud, pero el objetivo es actualizar todos los campos del recurso identificado.
@PutMapping("/usuarios/{id}")
public ResponseEntity<Usuario> actualizarUsuario(@PathVariable Long id, @RequestBody Usuario usuarioDetalles) {
Usuario usuarioExistente = servicioUsuario.buscarPorId(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado"));
usuarioExistente.setNombre(usuarioDetalles.getNombre());
usuarioExistente.setEmail(usuarioDetalles.getEmail());
usuarioExistente.setTelefono(usuarioDetalles.getTelefono());
// Actualizar otros campos necesarios
servicioUsuario.guardar(usuarioExistente);
return ResponseEntity.ok(usuarioExistente);
}
Aquí, @PathVariable
extrae el id
del usuario de la URL, y @RequestBody
recibe los nuevos detalles. Se verifica la existencia del usuario y se actualizan todos sus campos. La respuesta se envía con un código de estado 200 OK y el usuario actualizado en el cuerpo.
El método PATCH se utiliza para realizar actualizaciones parciales de un recurso. Spring Boot 3 proporciona la anotación @PatchMapping
para manejar estas solicitudes. Con PATCH, es posible modificar solo algunos campos sin afectar el resto del recurso.
@PatchMapping("/usuarios/{id}")
public ResponseEntity<Usuario> actualizarUsuarioParcialmente(@PathVariable Long id, @RequestBody Map<String, Object> campos) {
Usuario usuarioExistente = servicioUsuario.buscarPorId(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado"));
campos.forEach((clave, valor) -> {
Field campo = ReflectionUtils.findField(Usuario.class, clave);
if (campo != null) {
campo.setAccessible(true);
ReflectionUtils.setField(campo, usuarioExistente, valor);
}
});
servicioUsuario.guardar(usuarioExistente);
return ResponseEntity.ok(usuarioExistente);
}
En este caso, se recibe un mapa de campos y valores a actualizar. Utilizando reflexión, se recorren los campos y se actualizan en el objeto usuarioExistente
. Es importante tener en cuenta las implicaciones de seguridad y validar los datos al utilizar este enfoque.
Al trabajar con @RequestBody, es esencial que la clase del objeto cuente con los getters y setters necesarios y que esté correctamente anotada para la serialización y deserialización. Esto garantiza que Spring pueda convertir adecuadamente entre el JSON recibido y el objeto Java.
También es posible especificar el tipo de contenido que el método puede consumir o producir utilizando los atributos consumes
y produces
en las anotaciones:
@PostMapping(value = "/usuarios", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Usuario> crearUsuario(@RequestBody Usuario usuario) {
// Lógica de creación
}
Esto asegura que el método solo manejará solicitudes con contenido JSON y devolverá respuestas en el mismo formato.
Es crucial recordar que los métodos PUT son idempotentes, lo que significa que realizar la misma operación varias veces producirá el mismo resultado. Por el contrario, los métodos POST no son idempotentes; múltiples solicitudes pueden crear múltiples recursos. Este comportamiento debe tenerse en cuenta al diseñar la API para que cumpla con los estándares HTTP.
El uso de DTOs (Data Transfer Objects) es una práctica recomendada para separar la representación interna de los datos de la exposición externa a través de la API. Esto permite controlar qué información se expone y aplicar validaciones específicas.
@PostMapping("/usuarios")
public ResponseEntity<UsuarioDTO> crearUsuario(@RequestBody @Valid UsuarioDTO usuarioDTO) {
Usuario usuario = mapper.convertirADominio(usuarioDTO);
Usuario nuevoUsuario = servicioUsuario.guardar(usuario);
UsuarioDTO respuestaDTO = mapper.convertirADTO(nuevoUsuario);
return ResponseEntity.status(HttpStatus.CREATED).body(respuestaDTO);
}
En este ejemplo, se utiliza un mapper para convertir entre el DTO y la entidad de dominio. La anotación @Valid
activa la validación de los campos según las restricciones establecidas, lo que ayuda a garantizar la integridad de los datos.
Al manejar actualizaciones parciales con PATCH, es recomendable definir clases específicas para los campos que se pueden modificar, en lugar de utilizar un mapa genérico. Esto facilita la validación y mejora la seguridad del método.
@PatchMapping("/usuarios/{id}")
public ResponseEntity<Usuario> actualizarEmailUsuario(@PathVariable Long id, @RequestBody @Valid EmailDTO emailDTO) {
Usuario usuarioExistente = servicioUsuario.buscarPorId(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado"));
usuarioExistente.setEmail(emailDTO.getEmail());
servicioUsuario.guardar(usuarioExistente);
return ResponseEntity.ok(usuarioExistente);
}
En este caso, EmailDTO
es una clase que solo contiene el campo email
, permitiendo una actualización enfocada y segura.
Al diseñar los endpoints de la API, es importante seguir las buenas prácticas y estándares establecidos. Utilizar correctamente las anotaciones @PostMapping, @PutMapping y @PatchMapping, junto con @RequestBody, @PathVariable y @Valid, contribuye a construir una API consistente y fácil de mantener.
Es fundamental también considerar aspectos de seguridad, como proteger los datos sensibles y validar adecuadamente las entradas del usuario. Aunque la validación profunda y la gestión de seguridad pueden abordarse en secciones más especializadas, es esencial tener presente su importancia al implementar estos métodos.
Métodos DELETE
En el desarrollo de APIs REST con Spring Boot 3, el método DELETE se utiliza para eliminar recursos del servidor. Para manejar solicitudes DELETE, se emplea la anotación @DeleteMapping
en los controladores, especificando la ruta del recurso a eliminar.
Por ejemplo, para eliminar un usuario por su identificador, se puede definir el siguiente método en el controlador:
@DeleteMapping("/usuarios/{id}")
public ResponseEntity<Void> eliminarUsuario(@PathVariable Long id) {
if (servicioUsuario.eliminarPorId(id)) {
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
En este ejemplo, @DeleteMapping("/usuarios/{id}")
indica que el método eliminarUsuario
responderá a las solicitudes DELETE dirigidas al endpoint /usuarios/{id}
, donde {id}
es el identificador del usuario a eliminar. La anotación @PathVariable
extrae el valor de {id}
de la URL y lo asigna al parámetro id
del método.
El método eliminarUsuario
utiliza el servicio servicioUsuario
para intentar eliminar al usuario con el id
proporcionado. Si la eliminación es exitosa, se devuelve una respuesta con el código de estado 204 No Content mediante ResponseEntity.noContent().build()
. Si el usuario no se encuentra, se devuelve una respuesta con el código de estado 404 Not Found utilizando ResponseEntity.notFound().build()
.
Es importante destacar que, según las convenciones de las APIs REST, una solicitud DELETE exitosa puede devolver diferentes códigos de estado. El código 204 No Content indica que la eliminación se realizó correctamente y que no hay contenido adicional que proporcionar. Alternativamente, si se desea retornar información sobre el recurso eliminado, se podría utilizar 200 OK junto con el cuerpo de la respuesta.
El método DELETE es idempotente, lo que significa que realizar la misma operación varias veces producirá el mismo efecto. Si se intenta eliminar un recurso que ya no existe, se puede optar por devolver un código 404 Not Found para indicar que el recurso no fue encontrado, o 204 No Content si se prefiere no revelar esa información.
Para manejar escenarios donde puedan ocurrir excepciones durante la eliminación, es posible lanzar excepciones como ResponseStatusException
con el código de estado HTTP correspondiente:
@DeleteMapping("/usuarios/{id}")
public ResponseEntity<Void> eliminarUsuario(@PathVariable Long id) {
try {
servicioUsuario.eliminarPorId(id);
return ResponseEntity.noContent().build();
} catch (UsuarioNoEncontradoException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado");
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Error al eliminar el usuario");
}
}
En este fragmento de código, si ocurre una UsuarioNoEncontradoException
, se lanza una ResponseStatusException
con el estado 404 Not Found. Para otras excepciones, se utiliza 500 Internal Server Error, proporcionando un mayor control sobre los posibles errores.
Es esencial implementar mecanismos de autenticación y autorización para proteger los endpoints que permiten eliminar recursos. Solo los usuarios con los permisos adecuados deben poder realizar operaciones DELETE, evitando así la eliminación no autorizada de datos sensibles.
En casos donde se requiera eliminar múltiples recursos en una sola operación, se puede diseñar un endpoint que acepte una lista de identificadores:
@DeleteMapping("/usuarios")
public ResponseEntity<Void> eliminarUsuarios(@RequestParam List<Long> ids) {
servicioUsuario.eliminarVarios(ids);
return ResponseEntity.noContent().build();
}
Aquí, @RequestParam List<Long> ids
permite recibir una lista de identificadores como parámetros de consulta en la URL, por ejemplo: /usuarios?ids=1&ids=2&ids=3
. El método eliminarVarios
del servicio se encarga de procesar esta lista y eliminar los usuarios correspondientes.
También es posible recibir la lista de identificadores en el cuerpo de la solicitud utilizando @RequestBody
:
@DeleteMapping("/usuarios")
public ResponseEntity<Void> eliminarUsuarios(@RequestBody List<Long> ids) {
servicioUsuario.eliminarVarios(ids);
return ResponseEntity.noContent().build();
}
Al utilizar @RequestBody
, se espera que el cliente envíe los identificadores en formato JSON, lo cual puede ser más adecuado si la lista es extensa.
Es recomendable validar los datos recibidos antes de proceder con la eliminación. Comprobar que los identificadores son válidos y que los recursos existen ayuda a prevenir errores y mantener la integridad de la aplicación.
En algunos casos, en lugar de realizar una eliminación física del recurso en la base de datos, se opta por una eliminación lógica. Esto implica marcar el recurso como inactivo o eliminado sin eliminarlo realmente de la base de datos. El controlador seguiría manejando la solicitud DELETE de la misma manera, pero el servicio de persistencia implementaría la lógica correspondiente para actualizar el estado del recurso.
Al diseñar APIs REST, es fundamental seguir prácticas recomendadas y estándares establecidos. Garantizar que los métodos DELETE están correctamente implementados y que manejan adecuadamente las diferentes situaciones contribuye a crear una API robusta y confiable.
Finalmente, es importante considerar las implicaciones de seguridad y privacidad al exponer endpoints de eliminación. Asegurarse de que solo usuarios autorizados pueden acceder a estos endpoints y manejar con cuidado la información devuelta en las respuestas ayuda a proteger los datos y mantener la confianza en la aplicación.
Gestión de excepciones en controladores REST con @RestControllerAdvice
En el desarrollo de APIs REST con Spring Boot 3, la gestión adecuada de las excepciones es fundamental para proporcionar respuestas claras y coherentes a los clientes. La anotación @RestControllerAdvice
ofrece una forma elegante de manejar las excepciones de manera centralizada en los controladores REST, garantizando que los errores se gestionen de forma consistente y manteniendo el código limpio y modular.
La anotación @RestControllerAdvice
es una especialización de @ControllerAdvice
que combina esta última con @ResponseBody
. Esto significa que los métodos dentro de una clase anotada con @RestControllerAdvice
devolverán las respuestas directamente en el cuerpo de la respuesta HTTP, generalmente en formato JSON, sin necesidad de añadir @ResponseBody
en cada método.
A continuación, se muestra cómo definir una clase de manejo global de excepciones:
package com.ejemplo.demo.excepciones;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ManejadorGlobalExcepciones {
// Métodos de manejo de excepciones
}
En esta clase, se pueden definir métodos que intercepten excepciones específicas lanzadas en cualquier controlador REST. Para ello, se utiliza la anotación @ExceptionHandler
sobre cada método, indicando el tipo de excepción que se desea manejar.
Por ejemplo, para manejar la excepción EntityNotFoundException
y devolver una respuesta con estado 404 Not Found:
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(EntityNotFoundException.class)
public MensajeError manejarEntidadNoEncontrada(EntityNotFoundException ex) {
return new MensajeError("Recurso no encontrado", ex.getMessage());
}
En este método, se captura la excepción EntityNotFoundException
y se devuelve un objeto MensajeError
personalizado. La clase MensajeError
podría definirse de la siguiente manera:
public class MensajeError {
private String error;
private String descripcion;
public MensajeError(String error, String descripcion) {
this.error = error;
this.descripcion = descripcion;
}
// Getters y setters
}
Al utilizar @ResponseStatus(HttpStatus.NOT_FOUND)
, se establece el código de estado HTTP que se enviará en la respuesta. De este modo, cuando una EntityNotFoundException
sea lanzada en cualquier parte de los controladores REST, este método interceptará la excepción y devolverá una respuesta adecuada al cliente.
Para manejar múltiples excepciones con un solo método, se pueden especificar varias clases de excepción en la anotación @ExceptionHandler
:
@ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public MensajeError manejarExcepcionesInvalidas(RuntimeException ex) {
return new MensajeError("Solicitud incorrecta", ex.getMessage());
}
Este método capturará tanto IllegalArgumentException
como IllegalStateException
, devolviendo un código de estado 400 Bad Request y un mensaje descriptivo.
Es posible acceder a detalles adicionales de la solicitud y la respuesta mediante la inyección de objetos como HttpServletRequest
y HttpServletResponse
en los métodos de manejo de excepciones:
import jakarta.servlet.http.HttpServletRequest;
@ExceptionHandler(AccesoNoAutorizadoException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public MensajeError manejarAccesoNoAutorizado(AccesoNoAutorizadoException ex, HttpServletRequest request) {
String ruta = request.getRequestURI();
return new MensajeError("Acceso denegado", "No tiene permisos para acceder a " + ruta);
}
En este ejemplo, se utiliza HttpServletRequest
para obtener la ruta de la solicitud y proporcionar un mensaje más informativo al cliente.
Para manejar excepciones no controladas y evitar exponer detalles internos de la aplicación, es recomendable definir un método genérico que capture Exception
:
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public MensajeError manejarExcepcionGeneral(Exception ex) {
return new MensajeError("Error interno del servidor", "Ocurrió un error inesperado");
}
De esta manera, cualquier excepción no prevista será interceptada, y se enviará una respuesta con el código 500 Internal Server Error sin revelar información sensible.
La priorización en el manejo de excepciones es importante. Los métodos en @RestControllerAdvice
se evalúan en función de la especificidad de la excepción capturada. Es decir, si una excepción coincide con varios métodos, se seleccionará el que maneje la excepción más específica.
Es posible especificar a qué controladores se aplica el @RestControllerAdvice
utilizando atributos como basePackages
o assignableTypes
:
@RestControllerAdvice(basePackages = "com.ejemplo.demo.controladores")
public class ManejadorExcepcionesEspecifico {
// Métodos de manejo de excepciones
}
Con basePackages
, se limita el alcance del manejador de excepciones a los controladores dentro del paquete indicado. Así, se puede tener diferentes manejadores para distintas partes de la aplicación.
Otra práctica recomendada es crear excepciones personalizadas que extiendan de RuntimeException
y utilizarlas para casos específicos:
public class RecursoNoEncontradoException extends RuntimeException {
public RecursoNoEncontradoException(String mensaje) {
super(mensaje);
}
}
Al lanzar esta excepción en los servicios o controladores, se puede manejar en el @RestControllerAdvice
correspondiente:
@ExceptionHandler(RecursoNoEncontradoException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public MensajeError manejarRecursoNoEncontrado(RecursoNoEncontradoException ex) {
return new MensajeError("Recurso no encontrado", ex.getMessage());
}
Además de los métodos de manejo de excepciones, es posible utilizar la anotación @ExceptionHandler
directamente en los controladores. Sin embargo, al centralizar la gestión en un @RestControllerAdvice
, se promueve la separación de responsabilidades y se evita la repetición de código.
Al emplear validaciones con la anotación @Valid
en los parámetros de los métodos, pueden surgir excepciones como MethodArgumentNotValidException
. Estas también pueden ser gestionadas en el @RestControllerAdvice
:
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.validation.FieldError;
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public MensajeError manejarValidacionInvalida(MethodArgumentNotValidException ex) {
StringBuilder errores = new StringBuilder();
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
errores.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ");
}
return new MensajeError("Validación fallida", errores.toString());
}
Este método recopila los errores de validación y los presenta en un formato legible para el cliente, facilitando la corrección de los datos enviados.
Es importante considerar la gestión de excepciones en relación con la seguridad. Por ejemplo, al manejar excepciones como AccessDeniedException
, se puede proporcionar información adecuada sin revelar detalles innecesarios:
import org.springframework.security.access.AccessDeniedException;
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public MensajeError manejarAccesoDenegado(AccessDeniedException ex) {
return new MensajeError("Acceso denegado", "No tiene permisos para realizar esta acción");
}
En aplicaciones multilocalizadas, se puede integrar la internacionalización en los mensajes de error utilizando MessageSource
para obtener los mensajes en el idioma correspondiente:
import org.springframework.context.MessageSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Locale;
@RestControllerAdvice
public class ManejadorGlobalExcepciones {
@Autowired
private MessageSource messageSource;
@ExceptionHandler(RecursoNoEncontradoException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public MensajeError manejarRecursoNoEncontrado(RecursoNoEncontradoException ex, Locale locale) {
String mensaje = messageSource.getMessage("error.recurso.no.encontrado", null, locale);
return new MensajeError(mensaje, ex.getMessage());
}
}
En este caso, el mensaje mostrado al cliente estará en el idioma especificado en la solicitud, mejorando la experiencia del usuario.
Al implementar la gestión de excepciones con @RestControllerAdvice
, se logra una API más consistente y fácil de mantener. Los clientes recibirán respuestas predecibles ante errores, lo que facilita el desarrollo y depuración de aplicaciones que consuman la API.
Es esencial también documentar adecuadamente las respuestas de error en la especificación de la API, empleando herramientas como OpenAPI y anotaciones como @ApiResponse
de Swagger, para que los consumidores tengan claridad sobre cómo manejar las distintas situaciones.
En resumen, @RestControllerAdvice
es una herramienta poderosa que permite manejar excepciones de manera centralizada y coherente en las APIs REST desarrolladas con Spring Boot 3. Su uso adecuado contribuye a crear aplicaciones robustas, seguras y fáciles de mantener, mejorando tanto la calidad del código como la experiencia de los usuarios que interactúan con la API.
Ejercicios de esta lección Controladores Spring REST
Evalúa tus conocimientos de esta lección Controladores Spring REST con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
API Query By Example (QBE)
Identificadores y relaciones JPA
Borrar datos de base de datos
Web y Test Starters
Métodos find en repositorios
Controladores Spring MVC
Inserción de datos
CRUD Customers Spring MVC + Spring Data JPA
Backend API REST con Spring Boot
Controladores Spring REST
Uso de Spring con Thymeleaf
API Specification
Registro de usuarios
Crear entidades JPA
Asociaciones en JPA
Asociaciones de entidades JPA
Integración con Vue
Consultas JPQL
Open API y cómo agregarlo en Spring Boot
Uso de Controladores REST
Repositorios reactivos
Inyección de dependencias
Introducción a Spring Boot
CRUD y JPA Repository
Inyección de dependencias
Vista en Spring MVC con Thymeleaf
Servicios en Spring
Operadores Reactivos
Configuración de Vue
Entidades JPA
Integración con Angular
API Specification
API Query By Example (QBE)
Controladores MVC
Anotaciones y mapeo en JPA
Consultas JPQL con @Query en Spring Data JPA
Repositorios Spring Data
Inyección de dependencias
Data JPA y Mail Starters
Configuración de Angular
Controladores Spring REST
Configuración de Controladores MVC
Consultas JPQL con @Query en Spring Data JPA
Actualizar datos de base de datos
Verificar token JWT en peticiones
Login de usuarios
Integración con React
Configuración de React
Todas las lecciones de SpringBoot
Accede a todas las lecciones de SpringBoot y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Spring Boot
Introducción Y Entorno
Spring Boot Starters
Introducción Y Entorno
Inyección De Dependencias
Introducción Y Entorno
Controladores Spring Mvc
Spring Web
Vista En Spring Mvc Con Thymeleaf
Spring Web
Controladores Spring Rest
Spring Web
Open Api Y Cómo Agregarlo En Spring Boot
Spring Web
Servicios En Spring
Spring Web
Clientes Resttemplate Y Restclient
Spring Web
Rxjava En Spring Web
Spring Web
Crear Entidades Jpa
Persistencia Spring Data
Asociaciones De Entidades Jpa
Persistencia Spring Data
Repositorios Spring Data
Persistencia Spring Data
Métodos Find En Repositorios
Persistencia Spring Data
Inserción De Datos
Persistencia Spring Data
Actualizar Datos De Base De Datos
Persistencia Spring Data
Borrar Datos De Base De Datos
Persistencia Spring Data
Consultas Jpql Con @Query En Spring Data Jpa
Persistencia Spring Data
Api Query By Example (Qbe)
Persistencia Spring Data
Api Specification
Persistencia Spring Data
Repositorios Reactivos
Persistencia Spring Data
Introducción E Instalación De Apache Kafka
Mensajería Asíncrona
Crear Proyecto Con Apache Kafka
Mensajería Asíncrona
Creación De Producers
Mensajería Asíncrona
Creación De Consumers
Mensajería Asíncrona
Kafka Streams En Spring Boot
Mensajería Asíncrona
Introducción A Spring Webflux
Reactividad Webflux
Spring Data R2dbc
Reactividad Webflux
Controlador Rest Reactivo Basado En Anotaciones
Reactividad Webflux
Controlador Rest Reactivo Funcional
Reactividad Webflux
Operadores Reactivos Básicos
Reactividad Webflux
Operadores Reactivos Avanzados
Reactividad Webflux
Cliente Reactivo Webclient
Reactividad Webflux
Introducción A Spring Security
Seguridad Con Spring Security
Seguridad Basada En Formulario En Mvc Con Thymeleaf
Seguridad Con Spring Security
Registro De Usuarios
Seguridad Con Spring Security
Login De Usuarios
Seguridad Con Spring Security
Verificar Token Jwt En Peticiones
Seguridad Con Spring Security
Seguridad Jwt En Api Rest Spring Web
Seguridad Con Spring Security
Seguridad Jwt En Api Rest Reactiva Spring Webflux
Seguridad Con Spring Security
Autenticación Y Autorización Con Anotaciones
Seguridad Con Spring Security
Testing Unitario De Componentes Y Servicios
Testing Con Spring Test
Testing De Repositorios Spring Data Jpa
Testing Con Spring Test
Testing Controladores Spring Mvc Con Thymeleaf
Testing Con Spring Test
Testing Controladores Rest Con Json
Testing Con Spring Test
Testing De Aplicaciones Reactivas Webflux
Testing Con Spring Test
Testing De Seguridad Spring Security
Testing Con Spring Test
Testing Con Apache Kafka
Testing Con Spring Test
Integración Con Angular
Integración Frontend
Integración Con React
Integración Frontend
Integración Con Vue
Integración Frontend
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué son los Controladores Spring REST y su papel en una aplicación Spring Boot.
- Aprender a configurar y utilizar Controladores Spring REST, incluyendo la definición de rutas, manejo de diferentes tipos de solicitudes HTTP, y el trabajo con parámetros de solicitud.
- Entender cómo los Controladores Spring REST se integran con el resto de una aplicación Spring Boot y cómo interactúan con otras características como la inyección de dependencias y la seguridad.
- Ser capaz de diseñar y construir Controladores Spring REST efectivos para manejar las necesidades específicas de su aplicación.