Cómo responder con un error HTTP 400 en un método Spring MVC @ResponseBody que devuelve String

8 minutos de lectura

avatar de usuario
Jonik

Estoy usando Spring MVC para una API JSON simple, con @ResponseBody enfoque basado como el siguiente. (Ya tengo una capa de servicio que produce JSON directamente).

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        // TODO: how to respond with e.g. 400 "bad request"?
    }
    return json;
}

En el escenario dado, ¿cuál es la forma más simple y limpia de responder con un error HTTP 400?

Me encontré con enfoques como:

return new ResponseEntity(HttpStatus.BAD_REQUEST);

… pero no puedo usarlo aquí ya que el tipo de retorno de mi método es String, no ResponseEntity.

avatar de usuario
Bassem Reda Zohdy

Cambia tu tipo de devolución a ResponseEntity<>y luego puede usar lo siguiente para 400:

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

Y para una solicitud correcta:

return new ResponseEntity<>(json,HttpStatus.OK);

Después de Spring 4.1, hay métodos auxiliares en ResponseEntity que podrían usarse como:

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);

y

return ResponseEntity.ok(json);

  • Ah, entonces puedes usar ResponseEntity así también. Esto funciona muy bien y es solo un simple cambio al código original, ¡gracias!

    – Jonik

    27 de abril de 2013 a las 16:22

  • es bienvenido en cualquier momento que pueda agregar un encabezado personalizado también verifique todos los constructores de ResponseEntity

    – Bassem Reda Zohdy

    27 de abril de 2013 a las 22:27

  • ¿Qué sucede si está pasando algo que no sea una cadena hacia atrás? ¿Como en un POJO u otro objeto?

    – mrshickadance

    18 de noviembre de 2014 a las 16:28

  • será ‘ResponseEntity

    – Bassem Reda Zohdy

    19 de noviembre de 2014 a las 10:29

  • Con este enfoque, ya no necesita la anotación @ResponseBody

    – Ilya Serbia

    3 de agosto de 2015 a las 12:26

avatar de usuario
apilador

Algo como esto debería funcionar, pero no estoy seguro de si hay una manera más simple o no:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        response.setStatus( HttpServletResponse.SC_BAD_REQUEST  );
    }
    return json;
}

  • ¡Gracias! Esto funciona y es bastante simple también. (En este caso, se podría simplificar aún más eliminando los elementos no utilizados) body y request parámetros.)

    – Jonik

    27 de abril de 2013 a las 9:18

avatar de usuario
Zutty

No es necesariamente la forma más compacta de hacer esto, pero en mi opinión es bastante limpia:

if(json == null) {
    throw new BadThingException();
}
...

@ExceptionHandler(BadThingException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public @ResponseBody MyError handleException(BadThingException e) {
    return new MyError("That doesn’t work");
}

Puede usar @ResponseBody en el método del controlador de excepciones si usa Spring 3.1+; de lo contrario, use un ModelAndView o algo.

@ResponseBody no funciona con @ExceptionHandler [SPR-6902] #11567

  • Lo sentimos, esto no parece funcionar. Produce un “error de servidor” HTTP 500 con un seguimiento de pila largo en los registros: ERROR org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Failed to invoke @ExceptionHandler method: public controller.TestController$MyError controller.TestController.handleException(controller.TestController$BadThingException) org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation ¿Falta algo en la respuesta?

    – Jonik

    27 de abril de 2013 a las 8:52


  • Además, no entendí completamente el punto de definir otro tipo personalizado (MyError). ¿Es eso necesario? Estoy usando la última primavera (3.2.2).

    – Jonik

    27 de abril de 2013 a las 8:54

  • Esto funciona para mi. yo suelo javax.validation.ValidationException en cambio. (Primavera 3.1.4)

    –Jerry Chen

    1 de octubre de 2013 a las 1:48


  • Esto es bastante útil en situaciones en las que tiene una capa intermedia entre su servicio y el cliente donde la capa intermedia tiene sus propias capacidades de manejo de errores. Gracias por este ejemplo @Zutty

    – StormeHawke

    9 dic 2014 a las 15:57

  • Esta debería ser la respuesta aceptada, ya que mueve el código de manejo de excepciones fuera del flujo normal y oculta HttpServlet*

    – lilalinux

    14 de septiembre de 2016 a las 12:17


avatar de usuario
Matsev

Cambiaría un poco la implementación:

Primero creo un UnknownMatchException:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
    public UnknownMatchException(String matchId) {
        super("Unknown match: " + matchId);
    }
}

Nótese el uso de @ResponseStatusque será reconocida por Spring’s ResponseStatusExceptionResolver. Si se lanza la excepción, creará una respuesta con el estado de respuesta correspondiente. (También me tomé la libertad de cambiar el código de estado a 404 - Not Found que me parece más apropiado para este caso de uso, pero puede ceñirse a HttpStatus.BAD_REQUEST Si te gusta.)


A continuación, cambiaría el MatchService tener la siguiente firma:

interface MatchService {
    public Match findMatch(String matchId);
}

Finalmente, actualizaría el controlador y delegaría a Spring’s MappingJackson2HttpMessageConverter para manejar la serialización JSON automáticamente (se agrega de forma predeterminada si agrega Jackson al classpath y agrega cualquiera @EnableWebMvc o <mvc:annotation-driven /> a su configuración. Ver el documentación de referencia):

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Match match(@PathVariable String matchId) {
    // Throws an UnknownMatchException if the matchId is not known
    return matchService.findMatch(matchId);
}

Tenga en cuenta que es muy común separar los objetos de dominio de los objetos de vista o DTO objetos. Esto se puede lograr fácilmente agregando una pequeña fábrica de DTO que devuelve el objeto JSON serializable:

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public MatchDTO match(@PathVariable String matchId) {
    Match match = matchService.findMatch(matchId);
    return MatchDtoFactory.createDTO(match);
}

Aquí hay un enfoque diferente. crear una costumbre Exception anotado con @ResponseStatuscomo el siguiente.

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found")
public class NotFoundException extends Exception {

    public NotFoundException() {
    }
}

Y tirarlo cuando sea necesario.

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new NotFoundException();
    }
    return json;
}

Consulte la documentación de Spring aquí: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-anotado-excepciones.

  • Este enfoque le permite finalizar la ejecución dondequiera que se encuentre en el seguimiento de la pila sin tener que devolver un “valor especial” que debe especificar el código de estado HTTP que desea devolver.

    – Muhammad Gelbana

    28 de noviembre de 2017 a las 5:34

  • El enlace está (efectivamente) roto. No hay nada sobre excepciones en él.

    -Peter Mortensen

    19 de junio a las 12:26

avatar de usuario
Pedro Mortensen

La forma más fácil es lanzar un ResponseStatusException:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
    return json;
}

  • Este enfoque le permite finalizar la ejecución dondequiera que se encuentre en el seguimiento de la pila sin tener que devolver un “valor especial” que debe especificar el código de estado HTTP que desea devolver.

    – Muhammad Gelbana

    28 de noviembre de 2017 a las 5:34

  • El enlace está (efectivamente) roto. No hay nada sobre excepciones en él.

    -Peter Mortensen

    19 de junio a las 12:26

avatar de usuario
norris

Como se menciona en algunas respuestas, existe la posibilidad de crear una clase de excepción para cada estado HTTP que desee devolver. No me gusta la idea de tener que crear una clase por estado para cada proyecto. Esto es lo que se me ocurrió en su lugar.

  • Cree una excepción genérica que acepte un estado HTTP
  • Crear un controlador de excepciones de consejos de controlador

Vayamos al código

package com.javaninja.cam.exception;

import org.springframework.http.HttpStatus;


/**
 * The exception used to return a status and a message to the calling system.
 * @author norrisshelton
 */
@SuppressWarnings("ClassWithoutNoArgConstructor")
public class ResourceException extends RuntimeException {

    private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    /**
     * Gets the HTTP status code to be returned to the calling system.
     * @return http status code.  Defaults to HttpStatus.INTERNAL_SERVER_ERROR (500).
     * @see HttpStatus
     */
    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    /**
     * Constructs a new runtime exception with the specified HttpStatus code and detail message.
     * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.
     * @param httpStatus the http status.  The detail message is saved for later retrieval by the {@link
     *                   #getHttpStatus()} method.
     * @param message    the detail message. The detail message is saved for later retrieval by the {@link
     *                   #getMessage()} method.
     * @see HttpStatus
     */
    public ResourceException(HttpStatus httpStatus, String message) {
        super(message);
        this.httpStatus = httpStatus;
    }
}

Luego creo una clase de consejos de controlador.

package com.javaninja.cam.spring;


import com.javaninja.cam.exception.ResourceException;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;


/**
 * Exception handler advice class for all SpringMVC controllers.
 * @author norrisshelton
 * @see org.springframework.web.bind.annotation.ControllerAdvice
 */
@org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {

    /**
     * Handles ResourceExceptions for the SpringMVC controllers.
     * @param e SpringMVC controller exception.
     * @return http response entity
     * @see ExceptionHandler
     */
    @ExceptionHandler(ResourceException.class)
    public ResponseEntity handleException(ResourceException e) {
        return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
    }
}

para usarlo

throw new ResourceException(HttpStatus.BAD_REQUEST, "My message");

http://javaninja.net/2016/06/lanzar-excepciones-mensajes-spring-mvc-controller/

  • Muy buen método. En lugar de una cadena simple, prefiero devolver un jSON con código de error y campos de mensaje.

    – Ismail Yavuz

    13 de diciembre de 2017 a las 14:29

  • Esta debería ser la respuesta correcta, un controlador de excepciones genérico y global con código de estado personalizado y mensaje: D

    – Pedro Silva

    28 de septiembre de 2018 a las 16:36

  • Los navegadores no enlazan el enlace: “Advertencia: riesgo potencial de seguridad por delante… El certificado de javaninja.net expiró el 18/11/2021”.

    -Peter Mortensen

    19 de junio a las 12:28

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad