Disable redirect to /error for certain urls - spring

I have created a springboot application that contains some Rest API endpoints in .../api/myEndpoints... and thymeleaf templates for some UI forms the user can interact with.
Since I added an errorController:
#Controller
#RequestMapping("/error")
public class ErrorController {
#RequestMapping(method = RequestMethod.GET)
public String index(Model model) {
return "error";
}
}
whenever an exception is being thrown in my RestControllers, I receive an empty white website containing the word "error". This maybe makes sense for the web frontend, but not for my api. For the API I want spring to output the standard JSON result e.g.:
{
"timestamp": 1473148776095,
"status": 400,
"error": "Bad request",
"exception": "java.lang.IllegalArgumentException",
"message": "A required parameter is missing (IllegalArgumentException)",
"path": "/api/greet"
}
When I remove the index method from the ErrorController, then I always receive the JSON output.
My question is: Is it somehow possible to exclude the automatic redirection to /error for all api urls (../api/*) only?
Thanks a lot.

There may be a better solution out there, until then... here's how you can achieve what you asked:
(1) Disable ErrorMvcAutoConfiguration
Add this to your application.properties:
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
(2) Define two ControllerAdvices
Since we disabled ErrorMvcAutoConfiguration, we need to catch the exception ourself. Create one advice to catch error for a specific package, and another advice to catch all other. They each redirect to a different url.
//Catch exception for API.
#ControllerAdvice(basePackageClasses = YourApiController.class)
#Order(Ordered.HIGHEST_PRECEDENCE)
public static class ErrorApiAdvice {
#ExceptionHandler(Throwable.class)
public String catchApiExceptions(Throwable e) {
return "/error/api";
}
}
//Catch all other exceptions
#ControllerAdvice
#Order(Ordered.LOWEST_PRECEDENCE)
public static class ErrorAdvice {
#ExceptionHandler(Throwable.class)
public String catchOtherExceptions() {
return "/error";
}
}
(3) create a controller to handle the error page
This is where you can have different logic in your error handling:
#RestController
public class MyErrorController {
#RequestMapping("/error/api")
public String name(Throwable e) {
return "api error";
}
#RequestMapping("/error")
public String error() {
return "error";
}
}
With Spring-Boot 1.4.x you can also implement ErrorViewResolver (see this doc):
#Component
public class MyErrorViewResolver implements ErrorViewResolver {
#Override
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map<String, Object> model) {
if("/one".equals(model.get("path"))){
return new ModelAndView("/errorpage/api");
}else{
return new ModelAndView("/errorpage");
}
}
}

Related

How to handle exceptions thrown in the service layer?

I'm working on a spring-boot application. I tried handling exceptions .But i guess there is something wrong about how I'm doing it because it always throws internal server error 500.
I tried setting up custom exception classes and also used response status codes with #ResponseStatus. But regardless of what the exception is it throws an internal server error only.
I'm using intellij and the message i've given in the exception is printed there but the response body is empty.This i guess must be because it is throwing an internal server error.
Controller class
#RequestMapping(value = "/attendance",method = RequestMethod.POST)
public ResponseEntity<?> enterAttendance(#RequestBody ViewDTO viewDTO) throws CustomException{
return new ResponseEntity<>(tempResultServices.handleAttendance(viewDTO),HttpStatus.OK);
}
}
Service layer
#Override
public TempResult handleAttendance(ViewDTO viewDTO) throws CustomException {
TempIdentity tempIdentity=new TempIdentity();
tempIdentity.setRegistrationNo(viewDTO.getRegistrationNo());
tempIdentity.setCourseId(viewDTO.getCourseId());
tempIdentity.setYear(viewDTO.getYear());
tempIdentity.setSemester(viewDTO.getSemester());
User user=userService.findByUserId(viewDTO.getUserId());
tempIdentity.setUser(user);
if(!viewDTO.isAttendance()){
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance(),0);
ResultIdentity resultIdentity=new ResultIdentity(tempIdentity.getRegistrationNo(),tempIdentity.getCourseId(),tempIdentity.getYear(),tempIdentity.getSemester());
Result result=new Result(resultIdentity,0,"E*");
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
resultRepository.save(result);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
else{
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance());
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
}
The exception class
#ResponseStatus(code= HttpStatus.NOT_FOUND)
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
The terminal in the intellij prints "No draft available ". But i want it not as an internal server error.
Can some one tell me how i should be handling these errors please?
I tried using the #RestControllerAdvice
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomException.class)
public ResponseMsg handleNotFoundException(CustomException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
And this is my response message class
public class ResponseMsg {
private String message;
//getters and setters
}
This is another simple request in the application
#RequestMapping(value = "/user/view",method = RequestMethod.POST)
public ResponseEntity<?> getUser(#RequestBody UserDTO userDTO) throws CustomException{
User user=userService.findByUsername(userDTO.getUsername());
if(user!=null){
return ResponseEntity.ok(user);
}
//
throw new CustomException("User not found");
}
But still the custom exception is not thrown. The response body is empty. but intellij says "user not found" and postman returns the status code 500.
Spring boot has a very convenient way to handle exceptions in any layer of your application which is defining a #ControllerAdvice bean. Then you can throw any type of exception in your code and it will be "captured" on this class.
After this you can handle and return whatever your app needs to return.
By the way, you can return your custom object and it will be parsed to json automatically.
Documentation: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Sample code:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Object processValidationError(BadRequestException ex) {
//return whatever you need to return in your API
}
}

How can I modify default Json error response Spring?

When an item that doesn't exist in my web app is invoked through an URL, Spring responds with a JSON with data like (timestand, status, error, message, path). So, I need to change the structure of this JSON, specificly I need to remove path.
How can I do it?
Where should I implement the customization of the exception in my project?
Best regards to everyone!
Json response to modify
It's pretty easy in Spring MVC applications to handle errors by their types using the #ContollerAdvice class.
You could define your own handler for the exceptions you get on a method calls.
E.g.:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(value = ExceptionToHandle.class)
#ResponseBody
public YourResponse handle(ExceptionToHandle ex) {
return new YourResponse(ex.getMessage());
}
}
Here YourResponse is just a POJO, that could have any structure your want to be presented at the client.
The #ExceptionHandler specifies what types of errors will be handled in the method (including more specific types).
The #ResponseBody says that your returned value will be presented in the JSON format in your response.
You may try something like that:
#RestController
#RequestMapping("/")
public class TestController {
#GetMapping("/exception")
void getException() {
throw new MyCustomException("Something went wrong!");
}
class MyCustomException extends RuntimeException {
MyCustomException(String message) {
super(message);
}
}
class CustomError {
private String message;
private Integer code;
CustomError(String message, Integer code) {
this.message = message;
this.code = code;
}
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}
#ExceptionHandler(MyCustomException.class)
public CustomError handleMyCustomException(Exception ex) {
return new CustomError("Oops! " + ex.getMessage(), HttpStatus.BAD_REQUEST.value());
}
}
Fast and simple, you can just make your own exception and your own error object (which is later turned to json).
If you ask where to put such a thing... Well, you can make a separate class for the exception (and an exception package also), and put a small #ExceptionHandler method inside your controller. If you don't want it to be in the same class, you may delegate it to separate class also; for further and in-depth reading, look up for annotation like #ControllerAdvice.

Return response messages in spring boot

I am working with spring boot with a h2 database. I would like to return a 201 message when the register is inserted succesfully and a 400 when is duplicated. I am using ResponseEntity to achieve this, fot example , the next is my create method from the Service:
#Override
public ResponseEntity<Object> createEvent(EventDTO eventDTO) {
if (eventRepository.findOne(eventDTO.getId()) != null) {
//THis is a test, I am looking for the correct message
return new ResponseEntity(HttpStatus.IM_USED);
}
Actor actor = actorService.createActor(eventDTO.getActor());
Repo repo = repoService.createRepo(eventDTO.getRepo());
Event event = new Event(eventDTO.getId(), eventDTO.getType(), actor, repo, createdAt(eventDTO));
eventRepository.save(event);
return new ResponseEntity(HttpStatus.CREATED);
}
This is my controller:
#PostMapping(value = "/events")
public ResponseEntity addEvent(#RequestBody EventDTO body) {
return eventService.createEvent(body);
}
But I'm not getting any message in the browser, I am doing different tests with postman and when I consult for all the events, the result is correct, but each time that I make a post I dont get any message in the browser, I am not pretty sure what is the cause of this issue. Any ideas?
The ideal way to send Response to the client is to create DTO/DAO with ResponseEntity in Controller
Controller.java
#PostMapping("/test")
public ResponseEntity<Object> testApi(#RequestBody User user)
{
System.out.println("User: "+user.toString());
return assetService.testApi(user);
}
Service.java
public ResponseEntity testApi(User user) {
if(user.getId()==1)
return new ResponseEntity("Created",HttpStatus.CREATED);
else
return new ResponseEntity("Used",HttpStatus.IM_USED);
// for BAD_REQUEST(400) return new ResponseEntity("Bad Request",HttpStatus.BAD_REQUEST);
}
Tested using Postman
Status 201 Created
Status 226 IM Used
Okay, I really don't feel good that service sending the ResponseEntity but not Controller.You could use #ResponseStatus and ExceptionHandler classes for these cases, like below.
Create a class in exception package
GlobalExceptionHandler.java
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(DataIntegrityViolationException.class) // NOTE : You could create a custom exception class to handle duplications
public void handleConflict() {
}
}
Controller.java
#PostMapping(value = "/events")
#ResponseStatus(HttpStatus.CREATED) // You don't have to return any object this will take care of the status
public void addEvent(#RequestBody EventDTO body) {
eventService.createEvent(body);
}
Now changing the service would look like,
Service.java
#Override
public void createEvent(EventDTO eventDTO) { // No need to return
if (eventRepository.findOne(eventDTO.getId()) != null) {
throw new DataIntegrityViolationException("Already exists"); // you have to throw the same exception which you have marked in Handler class
}
Actor actor = actorService.createActor(eventDTO.getActor());
Repo repo = repoService.createRepo(eventDTO.getRepo());
Event event = new Event(eventDTO.getId(), eventDTO.getType(), actor, repo, createdAt(eventDTO));
eventRepository.save(event);
}

Change Spring Boots default JSON error response structure

I have an API built with Spring Boot. By default the default JSON structure when an error is thrown by Spring is;
{
"timestamp": 1477425179601,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/categoriess"
}
This structure is different to error responses returning myself in the API, so I'd like to change Spring to use the same structure as my own for consistency.
My error response are structured like this;
{
"errors": [
{
"code": 999404,
"message": "The resource you were looking for could not be found"
}
]
}
How would I go about doing this? I've tried using an Exception Handler, but I can't figure out the correct exception to set it up for. I'd like to also make sure that the Http status is still correctly returned as 404, or whatever the error is (500 etc).
I had another look at this and did manage to put something together that works for me.
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Map<String, Object> error = new HashMap<>();
error.put("code", errorAttributes.get("status"));
error.put("message", errorAttributes.get("error"));
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("errors", error);
return errorResponse;
}
};
}
This returns the following JSON response along with whatever header/http status code spring was going to return.
{
"errors": {
"code": 404,
"message": "Not Found"
}
}
This seems to work great for errors generated by spring, while my own Exceptions I'm handling in Controllers or in a specific ControllerAdmin class with ExceptionHandlers.
A possible way to do something like this is to use the #ExceptionHandler annotation to create a handler method inside your controller.
#RestController
#RequestMapping(produces = APPLICATION_JSON_VALUE)
public class MyController {
#RequestMapping(value = "/find", method = GET)
public Object find() {
throw new UnsupportedOperationException("Not implemented yet!");
}
#ExceptionHandler
public ErrorListModel handleException(Exception exception) {
ExceptionModel exceptionModel = new ExceptionModel(1337, exception.getMessage());
ErrorListModel list = new ErrorListModel();
list.add(exceptionModel);
return list;
}
private class ErrorListModel {
private List<ExceptionModel> errors = new ArrayList<>();
public void add(ExceptionModel exception) {
errors.add(exception);
}
public List<ExceptionModel> getErrors() {
return errors;
}
}
private class ExceptionModel {
private int code;
private String message;
public ExceptionModel(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}
The private classes ErrorListModel and ExceptionModel just help defining how the resulting JSON body should look, and I assume you already have your own, similar classes.
The find method just throws an exception for us to handle, which gets intercepted by the handleException method because it's annotated with #ExceptionHandler. In here, we create an ExceptionModel, populate it with information from the original exception, and add it to an ErrorListModel, which we then return.
This blog post from 2013 explains the features better than I ever could, and it also mentions an additional option, #ControllerAdvice. It basically allows you to re-use the exception handling in other controllers as well.

Spring Boot Rest Controller how to return different HTTP status codes?

I am using Spring Boot for a simple REST API and would like to return a correct HTTP statuscode if something fails.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
#ResponseBody
#ResponseStatus( HttpStatus.OK )
public RestModel create(#RequestBody String data) {
// code ommitted..
// how do i return a correct status code if something fails?
}
Being new to Spring and Spring Boot, the basic question is how do i return different status codes when something is ok or fails?
There are several options you can use. Quite good way is to use exceptions and class for handling called #ControllerAdvice:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
Also you can pass HttpServletResponse to controller method and just set response code:
public RestModel create(#RequestBody String data, HttpServletResponse response) {
// response committed...
response.setStatus(HttpServletResponse.SC_ACCEPTED);
}
Please refer to the this great blog post for details: Exception Handling in Spring MVC
NOTE
In Spring MVC using #ResponseBody annotation is redundant - it's already included in #RestController annotation.
One of the way to do this is you can use ResponseEntity as a return object.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
if(everything_fine) {
return new ResponseEntity<>(RestModel, HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
A nice way is to use Spring's ResponseStatusException
Rather than returning a ResponseEntityor similar you simply throw the ResponseStatusException from the controller with an HttpStatus and cause, for example:
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cause description here");
This results in a response to the client containing the HTTP status:
{
"timestamp": "2020-07-09T04:43:04.695+0000",
"status": 400,
"error": "Bad Request",
"message": "Cause description here",
"path": "/test-api/v1/search"
}
Note: HttpStatus provides many different status codes for your convenience.
In case you want to return a custom defined status code, you can use the ResponseEntity as here:
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
int customHttpStatusValue = 499;
Foo foo = bar();
return ResponseEntity.status(customHttpStatusValue).body(foo);
}
The CustomHttpStatusValue could be any integer within or outside of standard HTTP Status Codes.
Try this code:
#RequestMapping(value = "/validate", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ErrorBean> validateUser(#QueryParam("jsonInput") final String jsonInput) {
int numberHTTPDesired = 400;
ErrorBean responseBean = new ErrorBean();
responseBean.setError("ERROR");
responseBean.setMensaje("Error in validation!");
return new ResponseEntity<ErrorBean>(responseBean, HttpStatus.valueOf(numberHTTPDesired));
}
There are different ways to return status code,
1 : RestController class should extends BaseRest class, in BaseRest class we can handle exception and return expected error codes.
for example :
#RestController
#RequestMapping
class RestController extends BaseRest{
}
#ControllerAdvice
public class BaseRest {
#ExceptionHandler({Exception.class,...})
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorModel genericError(HttpServletRequest request,
HttpServletResponse response, Exception exception) {
ErrorModel error = new ErrorModel();
resource.addError("error code", exception.getLocalizedMessage());
return error;
}
I think the easiest way is to make return type of your method as
ResponseEntity<WHATEVER YOU WANT TO RETURN>
and for sending any status code, just add return statement as
return ResponseEntity.status(HTTP STATUS).build();
For example, if you want to return a list of books,
public ResponseEntity<List<books>> getBooks(){
List<books> list = this.bookService.getAllBooks();
if(list.size() <= 0)
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
else
return ResponseEntity.of(Optional.of(list));
}

Resources