Reputation: 3313
I have written simple REST application in Spring Boot
(Spring
Framework).
It returns ResponseEntity<Success>
as Success response in the controller level. But I want to return a completely different Error response ResponseEntity<Error>
, if there is an error (validation error, logic error, runtime error) in the API.
Success & Error responses are completely different for the application.
Success
& Error
are the 2 java classes which the application uses to represent Success & Error responses.
What is the best way to return different types of ResponseEntity
in Spring-Boot
(Best way to handle the errors for REST with Spring
)?
Upvotes: 124
Views: 310225
Reputation: 1
I faced the same issue where I need to send a string in case of an error but an object if successful. This is how I fixed it: Capture the response and use if condition.
Service Layer
@Service
public class MyService {
public Object myServiceMethod(boolean condition) {
if (condition) {
MyResponseObject responseObject = new MyResponseObject();
// Set properties on responseObject
return responseObject;
} else {
return "Some response string";
}
}
}
Controller Layer
@RestController
public class MyController {
@Autowired
private MyService myService;
@GetMapping("/my-endpoint")
public ResponseEntity<?> myControllerMethod(@RequestParam boolean condition) {
Object response = myService.myServiceMethod(condition);
if (response instanceof String) {
return ResponseEntity.ok(response);
} else {
return ResponseEntity.ok((MyResponseObject) response);
}
}
}
Upvotes: 0
Reputation: 11660
Using ResponseStatusException you can return error message, different HTTP status code, and DTO at the same time.
@PostMapping("/save")
public ResponseEntity<UserDto> saveUser(@RequestBody UserDto userDto) {
if(userDto.getId() != null) {
throw new ResponseStatusException(HttpStatus.NOT_ACCEPTABLE,"A new user cannot already have an ID");
}
return ResponseEntity.ok(userService.saveUser(userDto));
}
Upvotes: 18
Reputation: 71
You can return Map object from service layer and ResponseEntity from controller.
public Map<String, Object> serviceMethod(){
Map<String, Object> map = ...;
//...
if(error){
map.put("error", "some errors
has happened ");
}
if(error2){
map.put("error2", "some errors
has happened ");
}
return map;
map.put("success", true);
map.put("data", data);
return map;
}
Upvotes: 0
Reputation: 2209
As for spring boot 3.x, my preferred syntax is returning ResponseEntity<?>
(with generics) - choosing the HttpStatus
and body
according to the result/exception; very clean, easy to maintain and agnostic to the type of API/method I use.
E.g.
In this example, I'm returning either a User
dto or an INTERNAL_SERVER_ERROR
with the relevant error message:
@PostMapping("/signup")
public ResponseEntity<?> signup(@RequestBody User user) {
try {
return ResponseEntity.status(HttpStatus.OK).body(userService.addUser(user));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
Upvotes: 1
Reputation: 4504
You can use @ExceptionHandler Controller Advice to return custom Return Object in case error. See the example code below which returns 400 with Custom response in case of Validation Error.
@ControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(value = InputValidationError.class)
public ResponseEntity<ValidationErrorResponse> handleException(InputValidationError ex){
return ResponseEntity.badRequest().body(ex.validationErrorResponse);
}
}
Upvotes: 0
Reputation: 2740
To follow up the answer of @MarkNorman, I would say you have to define the mapping between the exceptions coming from your service
to your controller
(HTTP Error Code).
200
status (OK)400
status (BAD_REQUEST)404
status (NOT_FOUND)500
status (INTERNAL_SERVER_ERROR)For example, your code should look something like this:
@GetMapping("/persons/{id}")
public ResponseEntity<Success> findPersonById(@PathVariable("id") Long id) {
try {
var person = service.findById(id);
var message = new Message(HttpStatus.OK, getCurrentDateTime(), person);
return message;
} catch(ServiceException exception) {
throw new NotFoundException("An error occurs while finding a person", exception);
}
}
All the exceptions thrown should be redirect in the ControllerAdvice
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<Error> handleNotFoundException(NotFoundException exception) {
var error = new Error(HttpStatus.NOT_FOUND,
getCurrentDateTime(),
exception.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
The main thing to understand is that your RestControllers
only understand HTTP protocol and HTTP Code for responses.
Upvotes: 0
Reputation: 11660
Using custom exception class you can return different HTTP status code and dto objects.
@PostMapping("/save")
public ResponseEntity<UserDto> saveUser(@RequestBody UserDto userDto) {
if(userDto.getId() != null) {
throw new UserNotFoundException("A new user cannot already have an ID");
}
return ResponseEntity.ok(userService.saveUser(userDto));
}
Exception class
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "user not found")
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
Upvotes: 3
Reputation: 3620
For exceptional cases, I will recommend you to adopt RFC-7807 Problem Details for HTTP APIs standard in your application.
Zalando's Problems for Spring provides a good integration with Spring Boot, you can integrate it easily with your existing Spring Boot based application. Just like what JHipster did.
After adopting RFC-7087 in your application, just throw Exception in your controller method, and you will get a detailed and standard error response like:
{
"type": "https://example.com/probs/validation-error",
"title": "Request parameter is malformed.",
"status": 400
"detail": "Validation error, value of xxx should be a positive number.",
"instance": "/account/12345/msgs/abc",
}
Upvotes: 0
Reputation: 2451
I recommend using Spring's @ControllerAdvice
to handle errors. Read this guide for a good introduction, starting at the section named "Spring Boot Error Handling". For an in-depth discussion, there's an article in the Spring.io blog that was updated on April, 2018.
A brief summary on how this works:
ResponseEntity<Success>
. It will not be responsible for returning error or exception responses.@ControllerAdvice
@ExceptionHandler
ResponseEntity<Error>
With this approach, you only need to implement your controller exception handling in one place for all endpoints in your API. It also makes it easy for your API to have a uniform exception response structure across all endpoints. This simplifies exception handling for your clients.
Upvotes: 207
Reputation: 24780
Note: if you upgrade from spring boot 1 to spring boot 2 there is a ResponseStatusException
which has a Http error code and a description.
So, you can effectively use generics they way it is intended.
The only case which is a bit challenging for me, is the response type for a status 204 (ok with no body). I tend to mark those methods as ResponseEntity<?>
, because ResponseEntity<Void>
is less predictive.
Upvotes: 0
Reputation: 12817
You can return generic wildcard <?>
to return Success
and Error
on a same request mapping method
public ResponseEntity<?> method() {
boolean b = // some logic
if (b)
return new ResponseEntity<Success>(HttpStatus.OK);
else
return new ResponseEntity<Error>(HttpStatus.CONFLICT); //appropriate error code
}
@Mark Norman answer is the correct approach
Upvotes: 59
Reputation: 670
i am not sure but, I think you can use @ResponseEntity
and @ResponseBody
and send 2 different one is Success and second is error message like :
@RequestMapping(value ="/book2", produces =MediaType.APPLICATION_JSON_VALUE )
@ResponseBody
Book bookInfo2() {
Book book = new Book();
book.setBookName("Ramcharitmanas");
book.setWriter("TulasiDas");
return book;
}
@RequestMapping(value ="/book3", produces =MediaType.APPLICATION_JSON_VALUE )
public ResponseEntity<Book> bookInfo3() {
Book book = new Book();
book.setBookName("Ramayan");
book.setWriter("Valmiki");
return ResponseEntity.accepted().body(book);
}
For more detail refer to this: http://www.concretepage.com/spring-4/spring-4-mvc-jsonp-example-with-rest-responsebody-responseentity
Upvotes: 7
Reputation: 61
You can use a map with your object or string like bellow :
@RequestMapping(value = "/path",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<Map<String,String>> getData(){
Map<String,String> response = new HashMap<String, String>();
boolean isValid = // some logic
if (isValid){
response.put("ok", "success saving data");
return ResponseEntity.accepted().body(response);
}
else{
response.put("error", "an error expected on processing file");
return ResponseEntity.badRequest().body(response);
}
}
Upvotes: 6
Reputation: 1585
You can also implement like this to return Success and Error on a same request mapping method,use Object class(Parent class of every class in java) :-
public ResponseEntity< Object> method() {
boolean b = // logic here
if (b)
return new ResponseEntity< Object>(HttpStatus.OK);
else
return new ResponseEntity< Object>(HttpStatus.CONFLICT); //appropriate error code
}
Upvotes: 5
Reputation: 225
Here is a way that I would do it:
public ResponseEntity < ? extends BaseResponse > message(@PathVariable String player) { //REST Endpoint.
try {
Integer.parseInt(player);
return new ResponseEntity < ErrorResponse > (new ErrorResponse("111", "player is not found"), HttpStatus.BAD_REQUEST);
} catch (Exception e) {
}
Message msg = new Message(player, "Hello " + player);
return new ResponseEntity < Message > (msg, HttpStatus.OK);
}
@RequestMapping(value = "/getAll/{player}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity < List < ? extends BaseResponse >> messageAll(@PathVariable String player) { //REST Endpoint.
try {
Integer.parseInt(player);
List < ErrorResponse > errs = new ArrayList < ErrorResponse > ();
errs.add(new ErrorResponse("111", "player is not found"));
return new ResponseEntity < List < ? extends BaseResponse >> (errs, HttpStatus.BAD_REQUEST);
} catch (Exception e) {
}
Message msg = new Message(player, "Hello " + player);
List < Message > msgList = new ArrayList < Message > ();
msgList.add(msg);
return new ResponseEntity < List < ? extends BaseResponse >> (msgList, HttpStatus.OK);
}
Upvotes: 5
Reputation: 3313
Its possible to return ResponseEntity
without using generics, such as follows,
public ResponseEntity method() {
boolean isValid = // some logic
if (isValid){
return new ResponseEntity(new Success(), HttpStatus.OK);
}
else{
return new ResponseEntity(new Error(), HttpStatus.BAD_REQUEST);
}
}
Upvotes: 4
Reputation: 3666
I used to use a class like this. The statusCode is set when there is an error with the error message set in message. Data is stored either in the Map or in a List as and when appropriate.
/**
*
*/
package com.test.presentation.response;
import java.util.Collection;
import java.util.Map;
/**
* A simple POJO to send JSON response to ajax requests. This POJO enables us to
* send messages and error codes with the actual objects in the application.
*
*
*/
@SuppressWarnings("rawtypes")
public class GenericResponse {
/**
* An array that contains the actual objects
*/
private Collection rows;
/**
* An Map that contains the actual objects
*/
private Map mapData;
/**
* A String containing error code. Set to 1 if there is an error
*/
private int statusCode = 0;
/**
* A String containing error message.
*/
private String message;
/**
* An array that contains the actual objects
*
* @return the rows
*/
public Collection getRows() {
return rows;
}
/**
* An array that contains the actual objects
*
* @param rows
* the rows to set
*/
public void setRows(Collection rows) {
this.rows = rows;
}
/**
* An Map that contains the actual objects
*
* @return the mapData
*/
public Map getMapData() {
return mapData;
}
/**
* An Map that contains the actual objects
*
* @param mapData
* the mapData to set
*/
public void setMapData(Map mapData) {
this.mapData = mapData;
}
/**
* A String containing error code.
*
* @return the errorCode
*/
public int getStatusCode() {
return statusCode;
}
/**
* A String containing error code.
*
* @param errorCode
* the errorCode to set
*/
public void setStatusCode(int errorCode) {
this.statusCode = errorCode;
}
/**
* A String containing error message.
*
* @return the errorMessage
*/
public String getMessage() {
return message;
}
/**
* A String containing error message.
*
* @param errorMessage
* the errorMessage to set
*/
public void setMessage(String errorMessage) {
this.message = errorMessage;
}
}
Hope this helps.
Upvotes: 0