Spring Boot

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ícate

Qué 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.

Aprende SpringBoot GRATIS online

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)

Spring Boot
Test

Identificadores y relaciones JPA

Spring Boot
Puzzle

Borrar datos de base de datos

Spring Boot
Test

Web y Test Starters

Spring Boot
Puzzle

Métodos find en repositorios

Spring Boot
Test

Controladores Spring MVC

Spring Boot
Código

Inserción de datos

Spring Boot
Test

CRUD Customers Spring MVC + Spring Data JPA

Spring Boot
Proyecto

Backend API REST con Spring Boot

Spring Boot
Proyecto

Controladores Spring REST

Spring Boot
Código

Uso de Spring con Thymeleaf

Spring Boot
Puzzle

API Specification

Spring Boot
Puzzle

Registro de usuarios

Spring Boot
Test

Crear entidades JPA

Spring Boot
Código

Asociaciones en JPA

Spring Boot
Test

Asociaciones de entidades JPA

Spring Boot
Código

Integración con Vue

Spring Boot
Test

Consultas JPQL

Spring Boot
Código

Open API y cómo agregarlo en Spring Boot

Spring Boot
Puzzle

Uso de Controladores REST

Spring Boot
Puzzle

Repositorios reactivos

Spring Boot
Test

Inyección de dependencias

Spring Boot
Test

Introducción a Spring Boot

Spring Boot
Test

CRUD y JPA Repository

Spring Boot
Puzzle

Inyección de dependencias

Spring Boot
Código

Vista en Spring MVC con Thymeleaf

Spring Boot
Test

Servicios en Spring

Spring Boot
Código

Operadores Reactivos

Spring Boot
Puzzle

Configuración de Vue

Spring Boot
Puzzle

Entidades JPA

Spring Boot
Test

Integración con Angular

Spring Boot
Test

API Specification

Spring Boot
Test

API Query By Example (QBE)

Spring Boot
Puzzle

Controladores MVC

Spring Boot
Test

Anotaciones y mapeo en JPA

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Test

Repositorios Spring Data

Spring Boot
Test

Inyección de dependencias

Spring Boot
Puzzle

Data JPA y Mail Starters

Spring Boot
Test

Configuración de Angular

Spring Boot
Puzzle

Controladores Spring REST

Spring Boot
Test

Configuración de Controladores MVC

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Puzzle

Actualizar datos de base de datos

Spring Boot
Test

Verificar token JWT en peticiones

Spring Boot
Test

Login de usuarios

Spring Boot
Test

Integración con React

Spring Boot
Test

Configuración de React

Spring Boot
Puzzle

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

Spring Boot

Introducción Y Entorno

Spring Boot Starters

Spring Boot

Introducción Y Entorno

Inyección De Dependencias

Spring Boot

Introducción Y Entorno

Controladores Spring Mvc

Spring Boot

Spring Web

Vista En Spring Mvc Con Thymeleaf

Spring Boot

Spring Web

Controladores Spring Rest

Spring Boot

Spring Web

Open Api Y Cómo Agregarlo En Spring Boot

Spring Boot

Spring Web

Servicios En Spring

Spring Boot

Spring Web

Clientes Resttemplate Y Restclient

Spring Boot

Spring Web

Rxjava En Spring Web

Spring Boot

Spring Web

Crear Entidades Jpa

Spring Boot

Persistencia Spring Data

Asociaciones De Entidades Jpa

Spring Boot

Persistencia Spring Data

Repositorios Spring Data

Spring Boot

Persistencia Spring Data

Métodos Find En Repositorios

Spring Boot

Persistencia Spring Data

Inserción De Datos

Spring Boot

Persistencia Spring Data

Actualizar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Borrar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Consultas Jpql Con @Query En Spring Data Jpa

Spring Boot

Persistencia Spring Data

Api Query By Example (Qbe)

Spring Boot

Persistencia Spring Data

Api Specification

Spring Boot

Persistencia Spring Data

Repositorios Reactivos

Spring Boot

Persistencia Spring Data

Introducción E Instalación De Apache Kafka

Spring Boot

Mensajería Asíncrona

Crear Proyecto Con Apache Kafka

Spring Boot

Mensajería Asíncrona

Creación De Producers

Spring Boot

Mensajería Asíncrona

Creación De Consumers

Spring Boot

Mensajería Asíncrona

Kafka Streams En Spring Boot

Spring Boot

Mensajería Asíncrona

Introducción A Spring Webflux

Spring Boot

Reactividad Webflux

Spring Data R2dbc

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Basado En Anotaciones

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Funcional

Spring Boot

Reactividad Webflux

Operadores Reactivos Básicos

Spring Boot

Reactividad Webflux

Operadores Reactivos Avanzados

Spring Boot

Reactividad Webflux

Cliente Reactivo Webclient

Spring Boot

Reactividad Webflux

Introducción A Spring Security

Spring Boot

Seguridad Con Spring Security

Seguridad Basada En Formulario En Mvc Con Thymeleaf

Spring Boot

Seguridad Con Spring Security

Registro De Usuarios

Spring Boot

Seguridad Con Spring Security

Login De Usuarios

Spring Boot

Seguridad Con Spring Security

Verificar Token Jwt En Peticiones

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Spring Web

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Reactiva Spring Webflux

Spring Boot

Seguridad Con Spring Security

Autenticación Y Autorización Con Anotaciones

Spring Boot

Seguridad Con Spring Security

Testing Unitario De Componentes Y Servicios

Spring Boot

Testing Con Spring Test

Testing De Repositorios Spring Data Jpa

Spring Boot

Testing Con Spring Test

Testing Controladores Spring Mvc Con Thymeleaf

Spring Boot

Testing Con Spring Test

Testing Controladores Rest Con Json

Spring Boot

Testing Con Spring Test

Testing De Aplicaciones Reactivas Webflux

Spring Boot

Testing Con Spring Test

Testing De Seguridad Spring Security

Spring Boot

Testing Con Spring Test

Testing Con Apache Kafka

Spring Boot

Testing Con Spring Test

Integración Con Angular

Spring Boot

Integración Frontend

Integración Con React

Spring Boot

Integración Frontend

Integración Con Vue

Spring Boot

Integración Frontend

Accede GRATIS a SpringBoot y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender qué son los Controladores Spring REST y su papel en una aplicación Spring Boot.
  2. 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.
  3. 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.
  4. Ser capaz de diseñar y construir Controladores Spring REST efectivos para manejar las necesidades específicas de su aplicación.