Spring ControllerAdvice global exception handler not returning response - spring

I have a Groovy Spring Boot microservice that returns a list of posts. Requests come into the controller, the controller invokes a method in a service class, and if no posts are found, a custom error message is thrown.
I've created a controller annotated with #ControllerAdvice that I want to intercept errors, and a handler specific to the custom error. It should return a POJO. At present the ControllerAdvice handler is being invoked, but the response from the microservice is a 500 error
The controller
#RequestMapping(value ='getCommentList', method = RequestMethod.POST)
def getCommentList(#RequestBody requestParams) {
def response
String userId = requestParams.userId
String uuid = requestParams.uuid
response = commentService.findCommentsByUserIdAndUuid(userId, uuid)
}
The service method
def findCommentsByUserIdAndUuid(String userId, String uuid) {
User user = userRepository.findByUserId(userId)
Long userId = tenant?.id
List responses = commentRepository.findCommentsByUserAndUuidNotDeleted(userId, uuid)
if (responses.size() == 0) {
throw new CommentsNotFoundException()
} else {
def data = JsonOutput.toJson(retMap)
return data
}
}
the custom exception class
package com.news.exception
class CommentsNotFoundException extends Exception {
String errorMessage
def dataReturned
public commentsNotFoundException() {
NewsFeedMessage.buildRequestErrorMessageWithData(errorMessage, dataReturned)
}
}
The POGO to be returned
package com.news.utils
import org.springframework.http.HttpStatus
class NewsFeedMessage {
int httpStatus
String type
String message
def dataReturned
def subMessages = []
static NewsFeedMessage buildRequestErrorMessageWithData(mainMessage, data) {
return new NewsFeedMessage(
httpStatus: 400,
message: mainMessage,
dataReturned: data
)
}
}
And the global exception handler with#ControllerAdvice annotation
package com.news.exception
import com.newa.utils.NewsFeedMessage
import org.springframework.boot.configurationprocessor.json.JSON
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(CommentsNotFoundException.class)
public NewsFeedMessage handleException() {
def response = NewsFeedMessage.buildRequestErrorMessageWithData("testing 1234", [])
//return response as JSON
}
}
When debugging, The code gets as far as
def response = NewsFeedMessage.buildRequestErrorMessageWithData("testing 1234", [])
However nothing is returned.
The returned message using Postman is:
{
"timestamp": "2020-10-23T16:27:35.572+0000",
"status": 500,
"error": "Internal Server Error",
"message": "No message available",
"path": "/comment/getCommentList"
}
Is there anything here that is glaringly wrong?

I think NewsFeedMessage is not supported return type. Check ExceptionHandler docs and there is a block about supported return types. Try returning a ResponseEntity<>(new NewsFeedMessage()).

Related

How to get the status code of an exception from a ControllerAdvice in Spring Boo?

I'm using Spring Boot 3.x and I have a controller advice that looks like this:
#ControllerAdvice
class GlobalExceptionHandler {
#ExceptionHandler(Exception::class)
fun handleErrors(ex: Exception): ResponseEntity<ProblemDetail> {
val httpStatusCode = HttpStatusCode.valueOf(500)
val problemDetail = ProblemDetail.forStatusAndDetail(httpStatusCode, "error")
return ResponseEntity(problemDetail, httpStatusCode)
}
}
But if I get an exception, such as an org.springframework.security.access.AccessDeniedException, I want to return 403 instead of 500.
I know I can create a new method like this to return 403:
#ControllerAdvice
class GlobalExceptionHandler {
#ExceptionHandler(AccessDeniedException::class)
fun handleAccessDeniedErrors(ex: AccessDeniedException): ResponseEntity<ProblemDetail> {
val httpStatusCode = HttpStatusCode.valueOf(403)
val problemDetail = ProblemDetail.forStatusAndDetail(httpStatusCode, "error")
return ResponseEntity(problemDetail, httpStatusCode)
}
#ExceptionHandler(Exception::class)
fun handleErrors(ex: Exception): ResponseEntity<ProblemDetail> {
val httpStatusCode = HttpStatusCode.valueOf(500)
val problemDetail = ProblemDetail.forStatusAndDetail(httpStatusCode, "error")
return ResponseEntity(problemDetail, httpStatusCode)
}
}
But as you can see, the only thing that is different between the two methods is the status code. So my question is, how can I modify the original handleErros method to not only return 500 but rather return the status code that is expected for the exception by default (in the case of AccessDeniedException, that would be 403)?
I.e. I'm looking for the same status code that Spring Boot returns by default if I hadn't added the GlobalExceptionHandler bean. I'm not looking for something like manually mapping each exception type to a status code (using something like instanceof).

Why do I get random Http 404 from server between same requests with only one change in any field?

I haven an endpoint POST /api/marketplace/add that accepts a DTO object as request body. When I send the body below with platformName field set , server accepts request and processes it with no problem. But when I only try to change field platformName to null I get Http 404 error from server. I debugged the request and found out that it even can not reach controller method. I also got no trace from that error. What might be the cause that makes API respond differently to same request?
below
{
"platformName": "Trendyol",
"commissionAmounts": [
{
"amount": 23.45,
"categoryInfos": [
{
"categoryName": "Game"
}
],
"isCategoryBasedPricing": true
}
],
"shipmentAmounts": [
{
"amount": 23.45,
"scaleInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"volumeInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"isVolumeBasedPricing": true
}]
}
EDIT: dto model is
#Generated
public class MarketPlaceDTO {
#JsonProperty("platformName")
private String platformName;
#JsonProperty("commissionAmounts")
#Valid
private List<CommissionInfoDTO> commissionAmounts = new ArrayList<>();
#JsonProperty("shipmentAmounts")
#Valid
private List<ShipmentInfoDTO> shipmentAmounts = new ArrayList<>();
Controller is implementing swagger generated api interface. with postmapping and requestbody annotations.
#RequiredArgsConstructor
#RestController
public class MarketPlaceApiController implements MarketplaceApi {
private final MarketPlaceDAOService marketPlaceDAOService;
#Override
public ResponseEntity<BaseResponseDTO> addMarketPlace(MarketPlaceDTO
marketPlaceDTO) {
BaseResponseDTO dto =
marketPlaceDAOService.addMarketPlace(marketPlaceDTO);
return ResponseEntity.ok(dto);
}
}
Swagger generated api interface
#RequestMapping(
method = RequestMethod.POST,
value = "/marketplace/add",
produces = { "application/json", "application/xml" },
consumes = { "application/json" })
default ResponseEntity<BaseResponseDTO> _addMarketPlace(
#Parameter(name = "MarketPlaceDTO", description = "Add new
marketplace with given request body", required = true) #Valid
#RequestBody MarketPlaceDTO marketPlaceDTO) {
return addMarketPlace(marketPlaceDTO);
}
Response is
{
"timestamp": 1666866382906,
"status": 404,
"error": "Not Found",
"path": "/marketplace/add"
}
Obviously, that you use an endpoint with #RequestBody where body is a DTO.
And on trying to call this endpoint Spring Web first should match that a model in your request payload matches a require object in #RequestBody argument.
Ideally, using DTO as a request model is not a good idea. But I don't see your structure and cannot say if it's a problem or not.
The simple solution in your case is preparation (annotating) your DTO with specific JSON annotations:
#JsonInclude
#JsonIgnoreProperties(ignoreUnknown = true)
public class YourDTO {
private String platformName;
}
and for Controller add class annotation #Validated; for #RequestBody add #Valid annotation.
Recommendation: use request models for incoming objects, and later converters to DTO/entities with ability to response them with filtering (or in complex cases add also response model - usually it's overhead).
My problem was global exception handler component annotated with #ControllerAdvice. I tried to handle validation exceptions and forgot to add #ResponseBody to my handler methods which is in my case probabaly required. That somehow caused server to send http 404 message when any input validation exception was thrown. After I made changes , Exceptions was handled correctly by handler component.
#ControllerAdvice
#ResponseBody // this resolved my issue.
public class MVCExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseErrorResponse
methodArgumentExceptions(MethodArgumentNotValidException e){
return BaseErrorResponse.builder()
.errorMessage(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorMessage())
.errorCode(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorCode())
.errorTime(Date.from(Instant.now())).build();
}

Set response body in case of error in spring

I have an api in spring, returning a bytearray. In case any error happens, is it logical/possible to add a response body ?
eg:
#GetMapping(value = ["path"], produces = ["application/pdf"])
#ResponseBody
fun method(#PathVariable("varib") var: String?,
request: HttpServletRequest?, response:
HttpServletResponse?): ByteArray? {
// some method which will return a byte arrat
return pdf
}
#ExceptionHandler(RUnTimeException::class)
fun errorHandler(e: Exception,response: ServletRespons) {
response?.setHeader('someheader','value')
}
Is it possible to add a response body in case of exception ? from inside 'errorHandler' method ?is it logical ?
Depending on the version of Spring you are using, you don't need a #ResponseBody. You should rather use a #ControllerAdvice that help manage all your exceptions in your differents layers (repository, service, controller). You can define a custom response message that will represent your #Responsebody. I hope this link can help you.
You could add ControllerAdvice:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
#Slf4j
public class ExceptionHandlingAdvice {
private final Clock clock = Clock.systemDefaultZone();
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
return handleException(ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
private ResponseEntity<ErrorResponse> handleException(String message, HttpStatus httpStatus) {
log.error("Error: " + message);
ErrorResponse errorResponse = ErrorResponse.of(Instant.now(clock), message);
return new ResponseEntity<>(errorResponse, httpStatus);
}
}

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

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