Spring-MVC Exception handler returns OK when writing into response - spring

I'm using spring-webmvc : 3.2.3.RELEASE (and its related dependencies).
I have this controller:
#Controller
#RequestMapping("/home")
public class HomeController {
#Autowired
MappingJacksonHttpMessageConverter messageConverter;
#RequestMapping(method = RequestMethod.GET)
public String get() {
throw new RuntimeException("XXXXXX");
}
#ExceptionHandler(value = java.lang.RuntimeException.class)
#ResponseStatus(HttpStatus.CONFLICT)
public ModelAndView runtimeExceptionAndView(ServletWebRequest webRequest) throws Exception {
ModelAndView retVal = handleResponseBody("AASASAS", webRequest);
return retVal;
}
#SuppressWarnings({ "resource", "rawtypes", "unchecked" })
private ModelAndView handleResponseBody(Object body, ServletWebRequest webRequest) throws ServletException, IOException {
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
messageConverter.write(body, MediaType.APPLICATION_JSON, outputMessage);
return new ModelAndView();
}
}
since the "/home" method throws RuntimeException that is being handled with the #ExceptionHandler, when the get() method is invoked, I'm expectin to get HttpStatus.CONFLICT, but instead, I'm getting HttpStatus.OK.
Can someone please tell me what should I do in order to get the response status from
the annotated exception handler?

The reason is because you are explicitly writing to the output stream, instead of letting the framework handle it. The header has to go before the body content is written, if you are explicitly handling writing to the output stream, you will have to write the header also yourself.
To let the framework handle the entire flow, you can instead do this:
#ExceptionHandler(value = java.lang.RuntimeException.class)
#ResponseStatus(HttpStatus.CONFLICT)
#ResponseBody
public TypeToBeMarshalled runtimeExceptionAndView(ServletWebRequest webRequest) throws Exception {
return typeToBeMarshalled;
}

Modify ExceptionHandler method like this
#ExceptionHandler(value = java.lang.RuntimeException.class)
public ModelAndView runtimeExceptionAndView(ServletWebRequest webRequest, HttpServletResponse response) throws Exception {
response.setStatus(HttpStatus.CONFLICT.value());
ModelAndView retVal = handleResponseBody("AASASAS", webRequest);
return retVal;
}
If you want to handle exception by json result, I suggest to use #ResponseBody with Automatic Json return.
#ExceptionHandler(value = java.lang.RuntimeException.class)
#ResponseBody
public Object runtimeExceptionAndView(ServletWebRequest webRequest, HttpServletResponse response) throws Exception {
response.setStatus(HttpStatus.CONFLICT.value());
return new JsonResult();
}

Related

Get exception object in custom error controller

I am using spring boot and write a global exception handler use AbstractErrorController. How could i get an exception object in controller?
#Controller
public class MyCustomErrorController extends AbstractErrorController {
public MyCustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#RequestMapping("/error")
public void handleError(HttpServletRequest req, HttpServletResponse resp) {
Exception e = ...; // how to get exception here
log.error(e);
displayError(req, resp, e);
}
#Override
public String getErrorPath() {
return "/error";
}
}
You can get the exception from the HttpServletRequest as follows:
#Controller
public class MyCustomErrorController extends AbstractErrorController {
#RequestMapping("/error")
public void handleError(HttpServletRequest request) {
Exception e = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
...
}
}
An handler intercepts an Exception generated or re-thrown by a controller. It doesn't have an endpoint because it usually does it for all the controllers in your application. The Handler instructs the application server to return a specific error when a specific Exception is thrown.
Here is an example:
#ControllerAdvice // Specialization of #Component for classes that declare #ExceptionHandler, #InitBinder, or #ModelAttribute methods to be shared across multiple #Controller classes.
public class ResourceNotFoundExceptionHandler {
#ExceptionHandler(value = { ResourceNotFoundException.class })
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getLocalizedMessage(), ex);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
ResponseEntity<Object> response = new ResponseEntity<>(error, headers, HttpStatus.NOT_FOUND);
return response;
}
}
In this example ApiError is a data structure that reports the error to the UI. What this code does is intercepting the Exception "ResourceNotFoundException", create an appropriate error Data transfer object, set the response HttpStatus and headers and return the error.
you can find a different example here: https://github.com/otrebor/springbootseed-openshift/blob/master/src/main/java/com/company/example/springbootseed/core/errorhandling/handlers/
Add Exception as an extra parameter to handleError()

Spring validation returns long error messages, not just the customized message

Spring validation returns long error message instead of the customized once.
This is the section of code in the dto.
public class RequestDto implements Serializable {
#NotNull(message="{id.required}")
private Long id;
}
In controller added the #Valid for input.
#RequestMapping(value = ApiPath.PATH, method = RequestMethod.POST, produces = { "application/xml",
"application/json" })
public #ResponseBody ResultDecorator saveRequest(
#Valid #RequestBody RequestDto msaDisabScreenRequestDto) throws Exception {
}
API returns the following error.
<message>Validation failed for argument at index 0 in method: public om.gov.moh.msa.framework.resolver.ResultDecorator om.controller.MaController.saveRequest(om..dto.RequestDto) throws java.lang.Exception, with 1 error(s): [Field error in object 'requestDto' on field 'id': rejected value [null]; codes [NotNull.requestDto.id,NotNull.id,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestDto.id,id]; arguments []; default message [civilId]]; **default message [ID is required.]]** </message>
Here the custom message is present at the end. (default message [ID is required.)
Using Controller advice for global exception and I'm overriding handleMethodArgumentNotValid. How can I return only the custom message here?
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
apiError.setMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
}
You can get default/custom message like result.getFieldError("yourFieldName").getDefaultMessage()
You can catch error messages either through controller method which should look like this
#RequestMapping(value = ApiPath.PATH, method = RequestMethod.POST, produces = { "application/xml", "application/json" })
public #ResponseBody ResultDecorator saveRequest(#Valid #RequestBody RequestDto msaDisabScreenRequestDto, BindingResult result) throws Exception {
if(result.hasErrors()){
String errorMessage = result.getFieldError("yourFieldName").getDefaultMessage();
}
}
Or through Global Exception handler
Updated
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
//New Code
BindingResult bindingResult = ex.getBindingResult();
String errorMessage = result.getFieldError("yourFieldName").getDefaultMessage();
//---------------
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
apiError.setMessage(errorMessage);
return buildResponseEntity(apiError);
}
}
Thanks Afridi,
Created a string buffer and added all the error messages into that.
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
final StringBuffer errors = new StringBuffer();
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.append("\n");
errors.append(error.getField() + ": " + error.getDefaultMessage());
}
apiError.setMessage(errors.toString());
return buildResponseEntity(apiError);
}
As Afridi said in #ControllerAdvice can do this also:
#ExceptionHandler(value = MethodArgumentNotValidException.class)
#SuppressWarnings("unchecked")
#ResponseBody
public Result methodArgumentNotValidExceptionHandler(HttpServletRequest req, HttpServletResponse response, MethodArgumentNotValidException e) throws IOException {
String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
// todo return to your custom result
}
There are two point :
Exception class is MethodArgumentNotValidException
The first Error getDefaultMessage() can get your custom message in Annotation
In WebFlux :
Handle WebExchangeBindException for customising the default error message of #Valid
#ControllerAdvice
public class ValidationHandler {
#ExceptionHandler(WebExchangeBindException.class)
public ResponseEntity<List<String>> handleException(WebExchangeBindException e) {
var errors = e.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
}
Reference : https://www.vinsguru.com/spring-webflux-validation/

Spring MVC, redirect to error after Rest call exception

I'm performing a Rest request in one of my controllers and I'd like to redirect to my error view if the request went wrong (404, 503 ...)
My controller calls this function :
public String functionTest(){
String date, res;
String url = "myRestUrl/{param}";
Map<String, String> uriParams = new HashMap<>();
uriParams.put("param", "param");
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
HttpEntity<String> request = new HttpEntity<>(createHeaders());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestErrorHandler());
ResponseEntity<String> response = restTemplate.exchange(builder.buildAndExpand(uriParams).toUri(), HttpMethod.GET, request, String.class);
res= response.getBody().getMyResult();
return res;
}
And here is my Rest error handler :
public class RestErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
//Here I need to use a ModelAndView to redirect to error view but I'm not anymore in my controller
}
}
I guess I'm doing it wrong, do you have any solutions ?
Here is a rather simplistic way to achieve what you need. If you would like more control/flexibility, refer to this Spring blog article.
In your (client) RestErrorHandler:
public class RestErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response)
throws IOException {
// Do some stuff here ...
// This could be your own exception, for example.
throw new IOException();
}
}
Then in your controller (or refer to the article above if you want other options):
// Your requestMappings here ...
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
ModelAndView mav = new ModelAndView();
// Do stuff and redirect to your error view.
return mav;
}
EDIT: Another solution would be to catch Spring's RestClientException, which is a RuntimeException thrown by RestTemplate when it encounters an error:
try {
restTemplate.exchange(...);
} catch (RestClientException ex) {
// Do stuff here ...
}

No body in ResponseEntity when using spring annotation #ResponseStatus

I have an endpoint who throw an Exception using spring annotation, here is the code of my Exception :
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class MyException extends BaseApiException{
public MyException (String variable){
super("variable :"+variable+" can not be updated.");
}
}
When i use postman to test the Rest endpoint i get a correct result with a correct status code :
{
"errorType": "MyExption",
"message": "variable : XYZ can not be updated."
}
My problem is when i try to call the service using restTemplate I did not receive a body in the response, here is my code :
ResponseEntity<Document> response;
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(builder.build().encode().toUri(), HttpMethod.POST, entity, Document.class);
you need to define an error handler to extract this
public class MyResponseErrorHandler implements ResponseErrorHandler {
private static final Logger log = LoggerFactory.getLogger(MyResponseErrorHandler.class);
#Override
public void handleError(ClientHttpResponse response) throws IOException {
//here you should be able to get //response.getBody()
log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText());
}
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return isError(response.getStatusCode());
}
public static boolean isError(HttpStatus status) {
HttpStatus.Series series = status.series();
return (HttpStatus.Series.CLIENT_ERROR.equals(series)
|| HttpStatus.Series.SERVER_ERROR.equals(series));
}
}
RestTemplate template = new RestTemplate();
template.setErrorHandler(new MyResponseErrorHandler());

processFormSubmission not being called

I am new to spring. I am creating a simple login page. But the processFormSubmission() is not being called. But the showForm() is working.
public class LoginController extends SimpleFormController
{
private LoginService loginService;
private String loginView;
public LoginService getLoginService() {
return loginService;
}
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
public String getLoginView() {
return loginView;
}
public void setLoginView(String loginView) {
this.loginView = loginView;
}
public LoginController() {
setBindOnNewForm(true);
}
#Override
protected ModelAndView processFormSubmission(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception
{
TraceUser tr = (TraceUser) command;
System.out.println(tr);
//loginService.
return super.processFormSubmission(request, response, command, errors);
}
#Override
protected ModelAndView showForm(HttpServletRequest request,
HttpServletResponse response, BindException errors)
throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("traceUser", new TraceUser());
mav.setViewName(getLoginView());
return mav;
}
}
And please help me out with how should the ModelAndView object should be processed further.
First of all, the use of the Controller API has been left aside in favor of the new annotation-based controllers (see the #RequestMapping annotation) and classes like SimpleFormController have been deprecated since quite a while now.
However, to answer your question, I assume your form does not declare method="post" and by default, the SFC will consider only POST requests as form submissions (see the isFormSubmission() method in AbstractFormController). Is this the case ?

Resources