Best way of sending REST responses in spring boot - java

What is the best way to send rest responses in spring boot? Also how should i manage sending status codes to do it properly?
Currently i do it using ResponseEntity but i doubt this is the most elegant way.
Sample code:
#PostMapping()
public ResponseEntity post(#Valid #RequestBody Item item, BindingResult bindingResult){
if (bindingResult.hasErrors()){
return new ResponseEntity<>(new ModelErrors(bindingResult), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(itemService.addItem(item), HttpStatus.CREATED);
}
ModelErrors class extends a HashMap class and just fetches and wraps the BindingResult's error messages.

Personally I think that returning ResponseEntity is going to be the best choice for a lot of cases. A little more readable way of doing it in my opinion is to use the handy status methods on ResponseEntity like this
#PostMapping()
public ResponseEntity post(#Valid #RequestBody Item item, BindingResult bindingResult){
if (bindingResult.hasErrors()){
return ResponseEntity.badRequest().body(new ModelErrors(bindingResult));
}
return ResponseEntity.created().body(itemService.addItem(item));
}
Alternatively, you can use the status method passing a HttpStatus or status code like this
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ModelErrors(bindingResult));
ResponseEntity.status(201).body(itemService.addItem(item));
Another option is to just return whatever type you'd like without using ResponseEntity, but this gives you a lot less control over the response and requires that you have the proper MessageConverter configuration (you can read up on that here).
A simple example might look like this
#RequestMapping("/hotdog")
public Hotdog hotdog() {
return new Hotdog("mystery meat", "ketchup, mustard");
}
and if everything is configured correctly you'd end up with a response like this
{
"content": "mystery meat",
"condiments": "ketchup, mustard"
}

Related

Generic response for #RestController in Spring Boot?

In my Spring Boot app, I just use Optional for the first time and after examining several projects and topics, now I am trying to build an approach as shown below:
Repository:
Optional<Employee> findByEmail(String email);
Service:
public Response findByEmail(String email) {
return employeeRepository.findByEmail(email)
// if record is found, I think no need to return status or message
.map(e -> Response.builder().data(e).build())
.orElseGet(() -> Response.builder().status(404)
.data(null).message("Not found!").build());
}
Response:
#Data
#Builder
public class Response {
private int status;
private Object data;
private String message;
}
Controller:
#GetMapping("/employees/{email}")
public ResponseEntity<Response> findByEmail(#PathVariable String email) {
final Response response = employeeService.findByEmail(email);
return ResponseEntity
.status(response.getStatus())
.body(response.getMessage(), response.getData());
// throws "Expected 1 arguments but found 2" error
}
Here is the points that I need to be clarified:
1. Is this a proper approach to use a common response for all the Optional types in a Spring Boot app? If not, how should I change it (I want to return a common response from the Service)?
2. How to fix the throws "Expected 1 arguments but found 2" error in the Controller?
From my comment above - You are mixing concerns. Service is supposed to only care about business logic (e.g. not HTTP Status codes). That's controller's job. Use of Optional is correct, but the Response return type from service layer is not. Also errors like Not Found are automatically handled by a Rest Controller in Spring boot if a resource is not found. If you want to add custom logic and prepare generic responses, include a proper exception handling e.g. #ControllerAdvice (which allows you reuse exceptions for controllers).
As an example, one of the solutions would be to throw NoSuchElementException.
This is illustrative and would apply if you want to handle other such situations (e.g. null pointers, internal server error, authentication errors in a more custom manner) in a generic manner.
public Employee findByEmail(String email) {
return employeeRepository.findByEmail(email) //assuming findByEmail is returning an Optional<Employee>, otherwise - simply use a null check.
.orElseThrow(NoSuchElementException::new)
}
Inside #ControllerAdvice class
#ExceptionHandler(NoSuchElementException.class)
#ResponseBody
#Order(Ordered.HIGHEST_PRECEDENCE)
public final ResponseEntity<APIResponseErrorContainer> handleNotFound(
NoSuchElementException ex) {
// log exception here if you wish to
return new ResponseEntity<>(createCustomResponseBody(), HttpStatus.NOT_FOUND);
}

How to return CREATED status (201 HTTP) in ResponseEntity

There is a Spring-MVC application. In controllers, when returning the results of methods, I return via ResponseEntity<>. On success, I return (200 statutes) the OK-method. But when creating something, I would like to return the CREATED-method (201 status). I just can’t understand what kind of URL to ask in parentheses when calling through CREATED. How can this be implemented?
Now I have such an implementation:
#PostMapping("/create/dish")
ResponseEntity<Dish> createDish(#Valid #RequestBody DishDTO dishDTO) {
return ResponseEntity.ok(cookService.createDish(dishDTO.getDishName(), dishDTO.getAboutDish(), dishDTO.getDishType(),
dishDTO.getCookingTime(), dishDTO.getWeight(),
dishDTO.getDishCost(), dishDTO.getCooksId()));
}
I want to remake it like this to make it work(now it not work):
#PostMapping("/create/dish")
ResponseEntity<Dish> createDish(#Valid #RequestBody DishDTO dishDTO) {
return ResponseEntity.created(cookService.createDish(dishDTO.getDishName(), dishDTO.getAboutDish(), dishDTO.getDishType(),
dishDTO.getCookingTime(), dishDTO.getWeight(),
dishDTO.getDishCost(), dishDTO.getCooksId()));
}
P.S. I don’t have a frontend at all. All through Swagger or PostMan.
Just return this way:
return new ResponseEntity<Dish>(cookService.createDish(...), HttpStatus.CREATED)
Making sure you have imported org.springframework.http.HttpStatus
You can use
return new ResponseEntity(cookService.createDish(...), HttpStatus.CREATED);
Read more here
You can use
ResponseEntity.created(ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedObjectId).toUri()).build()
If you want to create a 201 (CREATED) response without a body, then use:
ResponseEntity.status(HttpStatus.CREATED).build()
Alternatively to return 201 (Created) status you can also dispose of ResponseEntity and set #ResponseStatus(HttpStatus.CREATED) for RestController method like this
#PostMapping("/create/dish")
#ResponseStatus(HttpStatus.CREATED)
Dish createDish(#Valid #RequestBody DishDTO dishDTO) {
return cookService.createDish(...);
}

Build a rest wrapper which accepts any kind of request

I want to build a rest wrapper in a spring boot application which accepts any kind of request (API call). Lets assume I have two API calls /employee/123 (GET method) /dept/123 (PUT method). Now When I hit these two requests from postman client, my wrapper should accepts these two types of requests.
I have tried this with Filter and Interceptor. But those didn't work for me. Can any one please explain how to do this.
Not quite clear what's your problem. Is this what you're looking for?
#RestController
public class SampleController {
#GetMapping(path = "/employee/{id}")
public String getEmployee(#PathVariable int id) {
....
}
#PutMapping(path = "/dept/{id}")
public String putDept(#PathVariable int id) {
....
}
}
Or you want an API proxy? So, perhaps, it makes sense to look at Zuul or any similar project?
if you want to accepts any kind of request like POST,GET,DELETE or PUT the dont mention the method of RequestMethod in #RequestMapping and if you want to do different operation depends on Request method then use HttpServletRequest for getting ReuestMethod
eg.
#RequestMapping({ "/employee/{id}", "/dept/{id}" })
public #ResponseBody String demo(HttpServletRequest request, #PathVariable("id") Integer id) {
if (request.getMethod().equalsIgnoreCase("POST")) {
return "POST MEhod";
} else if (request.getMethod().equalsIgnoreCase("GET")) {
return "GET Method";
} else if (request.getMethod().equalsIgnoreCase("PUT")) {
return "PUT Method";
} else {
return "DELETE Method";
}
}

Java/Spring > Handle Bad Request response for controller method with #RequestBody when no body is sent in request

long story short: I'm creating API that is supposed to be 100% REST.
I'm trying to overwrite default response for the following case:
I've got a method in my #RestController that has #RequestBody as an attribute
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(#RequestBody User user, HttpServletRequest request)
and the method is working just fine if I send a proper request. But there is a problem when I don't. When a request has empty body, I get a generic Tomcat error page for status 400 and I need it to send just a string or a JSON object instead.
So far I tried to add Exception Handlers in my RestControllerAdvice for all Spring exceptions from package org.springframework.web.binding, but it didn't work either.
I'm already aware that for some security-related errors one have to create handlers in configuration, but I don't know if this is the case.
Did anyone face similar issues? Is there something I'm missing?
The solution was to simply put required = false in RequestBody annotation. After that, I could easily add some logic to throw custom exception and handle it in ControllerAdvice.
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(#RequestBody(required = false) User user, HttpServletRequest request){
logger.debug("addClient() requested from {}; registration of user ({})", getClientIp(request), user);
if(user == null){
throw new BadRequestException()
.setErrorCode(ErrorCode.USER_IS_NULL.toString())
.setErrorMessage("Wrong body or no body in reqest");
} (...)
Firstly I suggest you to use BindingResult as a parameter of the POST call and check if it returns an error or not.
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public ResponseEntity<?> registerClient(#RequestBody User user, HttpServletRequest request, BindingResult brs)
if (!brs.hasErrors()) {
// add the new one
return new ResponseEntity<User>(user, HttpStatus.CREATED);
}
return new ResponseEntity<String>(brs.toString(), HttpStatus.BAD_REQUEST);
}
Secondly, the call can throw some of errors, a good practice is to carch them and return them itself or transform them to your own exception object. The advantage is it secures a call of all the update/modify methods (POST, PUT, PATCH)
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return new ResponseEntity<List<MethodArgumentNotValidException>>(e, HttpStatus.BAD_REQUEST);
}
#ExceptionHandler({HttpMessageNotReadableException.class})
#ResponseBody
public ResponseEntity<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
return new ResponseEntity<List<HttpMessageNotReadableException>>(e, HttpStatus.BAD_REQUEST);
}
Your control will never reach to your request method under normal circumstances.
If you want a looking good page you can make use of web.xml and configure it to produce your answer.
<error-page>
<error-code>404</error-code>
<location>/pages/resource-not-found.html</location>
</error-page>
Generally, if you want to go past this 400 problem, you will have to add a few annotiations to your User.java to avoid any unknown fields while de-serializing.

2 Request Handlers for POST (ResponseBody + "Normal")

I like to implement a REST-API into my SpringMVC application. At the moment, I have one method to handle POST-Requests, which "returns" a rendered ViewScript.
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
It would be nice, to add a second method with the #ResponseBody Annotation for POST-Requests, e.g. to send a JSON-Response.
Furthermore, the old Method still has to exists, to handle "normal" Requests.
But a code like this doesn't work:
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
#RequestMapping(method=RequestMethod.POST)
#ResponseBody
public Object add(User user, Model model)
{
// [...]
return myObject;
}
With this code, I'm getting a 405 (Method Not Allowed) Error from Tomcat. How can I fix this?
As it stands, Spring has no way to differentiate between these two requests: same URL, same request method.
You can further differentiate by mimetype:
#RequestMapping(method=RequestMethod.POST, headers="content-type=application/json")
Although there are several mimetypes associated with JSON :/ The headers value takes an array, however, so you can narrow/widen it as necessary.
See the headers docs.
Dont USE TWO ANNOTATION. It is a poor option. Just have one more method without annotation. But the method from the old method by checking the below condition.
JUST PASS ONE MORE ARGUMENT FROM UI by query parameter(request="JSON_Request").
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
if(request="JSON_Request") {
newMethod(user, model);
}
return "success";
}
private Object newMethod(User user, Model model)
{
// [...]
return myObject;
}

Categories