Spring boot #Valid is not returning proper message - spring-boot

Controller Method
#PostMapping("/hello")
public Hello hello(#Valid #RequestBody Hello hello) {
return hello;
}
POJO
import jakarta.validation.constraints.NotBlank;
class Hello{
#NotBlank(message = "msg must be present")
String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
Upon hitting the above URL with the following payload
{
"msg":""
}
I am getting the following response.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Invalid request content.",
"instance": "/hello"
}
It should ideally specify the message msg must be present.
What's wrong here?
The following things have been already tried
added server.error.include-message: always in application.properties file
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<Object>("ConstraintViolationException",
HttpStatus.BAD_REQUEST);
}
Thanks in advance 👏
Edit
I had a #RestControllerAdvice and it starts working fine, once i remove it. #RestControllerAdvice is needed in my case for the customization of exceptions.

You have to write a controller advice and return the interpolated message from the exception caught in handler for invalid method argument.
#RestControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
String bodyOfResponse = ex.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
return new ResponseEntity(bodyOfResponse, HttpStatus.BAD_REQUEST);
}
}
And after that you should get back message msg must be present along with 400. In case you have more constraints, you should iterate over field errors, and get(0) is only for demonstration purpose.

Related

Customizing NoHandlerException response with ControllerAdvice

I try to implement a custom error response in a spring boot rest application for 404 errors.
I read many of the solutions presented in stackoverflow, without success.
When I call an invalid entry point I obtain this result:
{
"timestamp": "2022-06-22T10:38:41.114+00:00",
"status": 404,
"error": "Not Found",
"path": "/ws-facturx/fx2"
}
But i'd like to have a response that should look like this:
{
"operationId": "u044eZg2gHwtadqxB5CVv6aeMBjj0w",
"status": "ERROR",
"operation": "webserviceName",
"clientName": "ACME Inc",
"errorMessage": "Error message from Server",
"createdAt": "2022-06-22T09:15:04.844+00:00"
}
I first tried to use #RestControllerAdvice to intercept the exception when they are thrown.
#ExceptionHandler(value = {AppServiceException.class, NoHandlerFoundException.class, ServletServiceException.class })
public ResponseEntity<Object> handleAppServiceException(Exception ex,
WebRequest req) throws JsonProcessingException {
FacturxDto request = context.getFacturxDtoContext();
ErrorMessage errorMessage = errorMessageBuilder(request, ex);
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I also modified my application.properties :
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
If i call a non defined entry point I do not reach this method. I tried to use an interceptor.
I firs added a class for adding interceptor to InterceptorRegistry:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final ApplicationExchangeContext context;
public WebMvcConfig(ApplicationExchangeContext context) {
this.context = context;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApplicationInterceptor(context)).addPathPatterns("/**");
}
}
My ApplicationInterception looks like this:
#Component
public class ApplicationInterceptor implements HandlerInterceptor {
private final ApplicationExchangeContext context;
#Autowired
public ApplicationInterceptor(ApplicationExchangeContext context) {
this.context = context;
}
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in preHandle");
return true;
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println(request);
System.out.println(response);
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in afterCompletion");
}
}
On the preHandle, i do reach the catch part of the code block but i do not access the RestControllerAdvice method that should handle this exception and build my expected object.
The exception is thrown. But i do not return it to user. Instead I do have an HTML page.

Interceptor and global exception handling

I have a post-interceptor. When the control layer is executed and returns information, the post-interceptor will be executed. At this time, an exception in the post-interceptor will be caught by the global exception handling and a prompt message will be returned. Use "postman" to test and control The information of layer and global exception handling is returned at the same time. Is this really returned? I wrote a test example. In the same situation, only the information of the control layer is returned. I think it should return the information of global exception handling.
Controller
#RestController
#RequestMapping("/v1/book")
#Validated
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
#GetMapping("/search")
public R searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return R.select(bookService.getBookByKeyword(q));
}
}
Interceptor
public class LogInterceptor extends HandlerInterceptorAdapter {
public LogInterceptor(LoggerResolver loggerResolver) {
this.loggerResolver = loggerResolver;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// There will be a runtime exception here
}
}
Global Exception Handing
#Order
#RestControllerAdvice
#Slf4j
public class RestExceptionHandler {
/**
* Exception
*/
#ExceptionHandler({Exception.class})
public R processException(Exception exception) {
log.error("", exception);
return R.error();
}
}
Result
{
"code": 200,
"data": [
// ...
],
"type": "success",
"message": "OK"
}{
"code": 500,
"type": "error",
"message": "Internal Server Error"
}
"R extends HashMap<String, Object>", used to unify the return structure.
looking at your code snippet, I'm not sure what are those R in the searchBook and processException
try this (edit the processException to meet your specs):
#GetMapping("/search")
public ResponseEntity<?> searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return new ResponseEntity<>(bookService.getBookByKeyword(q), HttpStatus.OK);
}
#ExceptionHandler({Exception.class})
public ResponseEntity<?> processException(Exception exception) {
return new ResponseEntity<>(new ErrorDTO(exception.getMessage()), HttpStatus.UNPROCESSABLE_ENTITY);
}

Why to use #ResponseBody with #ControllerAdvice in the case of RESTServices

I am learning about global exception handling in spring boot. I have a designed a controller annotated with #RestController which has a controller method that throws an exception. I have designed another class named GlobalExceptionHandling annotated with #RestControllerAdvice/#ControllerAdvice. It works fine and handles the exception when annotated with #RestControllerAdvice but doesn't work as expected when annotated with #ControllerAdvice. I am sharing my code and the responses i got on postman.
DemoController:
#RestController
public class DemoController {
#RequestMapping("exception/arithmetic")
public String controllerForArithmeticException()
{
throw new ArithmeticException("Divide by zero error");
}
#RequestMapping("exception")
public String controllerForException() throws Exception
{
throw new Exception("An exception occurred");
}
}
GlobalExceptionHandler: (with #RestControllerAdvice)
#RestControllerAdvice
public class GlobalExceptionHandler{
#ExceptionHandler(value = Exception.class)
public String handleException(Exception e)
{
return "Exception: " + e.getMessage();
}
#ExceptionHandler(value = ArithmeticException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleArithmeticException(ArithmeticException e)
{
return "ArithmeticException: " + e.getMessage();
}
}
Response on postman:
Status: 404 Bad Request
Response Body: ArithmeticException: Divide by zero error
Console: Nothing gets printed on console.
GlobalExceptionHandler: (with #ControllerAdvice)
#ControllerAdvice
public class GlobalExceptionHandler{
#ExceptionHandler(value = Exception.class)
public String handleException(Exception e)
{
return "Exception: " + e.getMessage();
}
#ExceptionHandler(value = ArithmeticException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleArithmeticException(ArithmeticException e)
{
return "ArithmeticException: " + e.getMessage();
}
}
Response on postman:
Status: 404 Bad Request
Response Body: {
"timestamp": "2020-02-15T12:41:40.988+0000",
"status": 404,
"error": "Not Found",
"message": "Divide by zero error",
"path": "/exception/arithmetic"
}
Console: Nothing gets printed on console.
Can you explain what exactly #ResponseBody do?

Spring boot custom exception not getting printed in logs

I am trying to implement a custom exception for my Spring boot REST project. The custom exception gets called but shows no impact in the way error message is displayed.
This is the POJO I'm using for my custom errors:
public class ApiError {
private HttpStatus status;
private String message;
private List<String> errors;
public ApiError(HttpStatus status, String message, List<String> errors) {
super();
this.status = status;
this.message = message;
this.errors = errors;
}
public ApiError(HttpStatus status, String message, String error) {
super();
this.status = status;
this.message = message;
errors = Arrays.asList(error);
}
}
This is the exception handler I wrote:
#ControllerAdvice
#EnableWebMvc
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
System.out.println("Custom exception!!");
//List<String> details = new ArrayList<>();
//details.add(ex.getLocalizedMessage());
//System.out.println("Localize message:: "+ex.getLocalizedMessage());
// ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
//request.getDescription(false));
ApiError error = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR,"Server Error", request.getDescription(false));
return new ResponseEntity<Object>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Additionally, I'm defined the following method within my controller:
#RequestMapping(value = "/model", params = "number", method = RequestMethod.GET, produces = "application/json")
List<Model> getModel(HttpServletRequest request,#RequestParam(value = "codeNumber") String number) throws Exception{
List<Model> model = null;
try {
model = niiService.getModel(number);
}catch(RuntimeException e){
new Exception(e);
}
return model;
}
However, in stead of my custom POJO, I'm seeing the following exception:
{
"timestamp": 1547013989124,
"status": 500,
"error": "Internal Server Error",
"message": "Request processing failed; nested exception is org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure\n\nThe last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.",
"path": "/model"
}
I was expecting the following JSON structure in stead:
{
"status": 500,
"message": "Server Error"
..
}
Please let me know, what I am missing to get the error response in the way I wanted.
You are missing the throw in front of the custom exception.
Also make sure that you are catching the right exception inside your controller.
Change
catch (RuntimeException e) {
//custom exception
new Exception(e);
}
To
catch (RuntimeException e) {
//custom exception
throw new Exception(e);
}
I missed adding getter and setter to APIError class. Hence, the response was not coming in the way I expected.

Spring boot Exception custom handling - Unexpected HTTP status

I am trying to implement some custom exception handlers in my spring boot application which will be able to handle custom exceptions and display appropiate message and status code.
My issue : Getting http status = 500 even though the response body is according to my custom handler.
Code :
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ BadRequestValidationFailureException.class, Exception.class })
public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) {
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getMessage());
return new ResponseEntity<Object>( apiError, new HttpHeaders(), HttpStatus.BAD_REQUEST );
}
And throwing exception as :
throw new BadRequestValidationFailureException( "ERROR_CODE", "THIS IS THE MESSAGE" );
The output is :
{
"timestamp": "2018-09-20T17:44:01.502Z",
"status": 500,
"error": "Internal Server Error",
"exception": "com.hotstar.payment.exception.BadRequestValidationFailureException",
"message": "[ ERROR_CODE ] THIS IS THE MESSAGE",
"path": "/my/api/path"
}
The weird thing is that the http response status is 500.
Please help.
Got the solution. Had to set another annotation :
#ResponseStatus(value = HttpStatus.BAD_REQUEST, code = HttpStatus.BAD_REQUEST, reason = "some reason")
Add this to handleAll method.
I've made good experiences with the following pattern:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
private static final Map<MyProjectErrorCode, HttpStatus> CODE_STATUS_MAP = new EnumMap<>(MyProjectErrorCode.class);
static {
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_ILLEGAL_PROPERTY, HttpStatus.BAD_REQUEST);
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_FOO, HttpStatus.BAD_REQUEST);
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_THIRDPARTYX_CLIENT, HttpStatus.INTERNAL_SERVER_ERROR);
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_UNKNOWN, HttpStatus.INTERNAL_SERVER_ERROR);
CODE_STATUS_MAP.put(MyProjectErrorCode.THIRDPARTYX_BAR, HttpStatus.BAD_REQUEST);
CODE_STATUS_MAP.put(MyProjectErrorCode.THIRDPARTYX_UNKNOWN, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(MyProjectException.class)
public ResponseEntity<ErrorResponse> handleMyProjectException(MyProjectException ex) {
ErrorResponse errorResponse = createErrorResponse(ex.getErrorCode(), ex.getMessage());
HttpStatus httpStatus = determineHttpStatus(ex.getErrorCode());
return handleErrorResponse(errorResponse, httpStatus);
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
MyProjectErrorCode errorCode = MyProjectErrorCode.MYPROJ_ILLEGAL_PROPERTY;
ErrorResponse errorResponse = createErrorResponse(errorCode, ex.getMessage());
HttpStatus httpStatus = determineHttpStatus(errorCode);
return handleErrorResponse(errorResponse, httpStatus);
}
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) {
MyProjectErrorCode errorCode = MyProjectErrorCode.MYPROJ_UNKNOWN;
ErrorResponse errorResponse = createErrorResponse(errorCode, ex.getMessage());
HttpStatus httpStatus = determineHttpStatus(errorCode);
return handleErrorResponse(errorResponse, httpStatus);
}
private ResponseEntity<ErrorResponse> handleErrorResponse(ErrorResponse errorResponse, HttpStatus httpStatus) {
return new ResponseEntity<>(errorResponse, httpStatus);
}
private ErrorResponse createErrorResponse(MyProjectErrorCode errorCode, String message) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setErrorCode(errorCode.name());
errorResponse.setErrorMessage(message);
return errorResponse;
}
private HttpStatus determineHttpStatus(MyProjectErrorCode errorCode) {
return CODE_STATUS_MAP.getOrDefault(errorCode, HttpStatus.INTERNAL_SERVER_ERROR);
}
The client can get the HttpStatus from the Http response - no need to add it to the JSON body.
The project specific MyProjectErrorCode enum allows you to communicate to the clients a detailed error code. The client can analyze this error code and take the appropriate action or display a localized (specific or generic) error message based on the error code.
MyProjectErrorCode also allows you to communicate if the error was created in your code (starting with MYPROJ_) or if the error is forwarded from the third party 'x' service (starting with THIRDPARTYX_).
You can also create subclasses of MyProjectException and ErrorResponse to transport more specific data for specific cases - just add an additional expcetion handler method for that exception.

Resources