Required parameters exception doesn't work in spring boot 2.x - spring

In my spring boot application, I tried to handled the Required parameter exception. This question may be duplicated. But the answers posted don't help me.
My controller
#GetMapping("/test")
public ObjectId test(#RequestBody OIdLGroupIds OIdLGroupIds,#RequestParam ObjectId _id){
return videoService.test();
}
My global exception handler is like following.
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse("Server Error", details);
return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(NotFoundHandler.class)
public final ResponseEntity<Object> handleRecordNotFoundException(NotFoundHandler ex, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", "Record not found");
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse("Record Not Found", details);
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDate.now());
body.put("status", status.value());
Set<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(x -> x.getDefaultMessage())
.collect(Collectors.toSet());
body.put("details", errors);
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
}
It doesn't throw any details (Body is blank). But the response status is 400 Bad Request. But when I comment all above codes, it throws default exceptions with body.
I tried this also
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String name = ex.getParameterName();
System.out.println(name);
logger.error(name + " parameter is missing");
return super.handleMissingServletRequestParameter(ex, headers, status, request);
}
I have tried in many ways, But no luck. Did I miss anything? Please help me. Thanks in advance.

Empty request body raises HttpMessageNotReadableException.
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
String name = ex.getParameterName();
System.out.println(name);
logger.error(name + " parameter is missing");
return super.handleMissingServletRequestParameter(ex, headers, status, request);
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println(ex.getMessage());
logger.error("Request body is missing");
return super.handleHttpMessageNotReadable(ex, headers, status, request);
}
}
If you allow empty body, use #RequestBody(required = false).

Related

what is the usage of extending ResponseEntityExceptionHandler?

can some one explain me what is the usage of extending ResponseEntityExceptionHandler. If Ido not extend also ResponseEntityExceptionHandler GlobalExceptionHandler is working and sending the response to client.
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler{
#ExceptionHandler({ UserNotFoundException.class, ContentNotAllowedException.class })
public final ResponseEntity<ApiError> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof UserNotFoundException) {
HttpStatus status = HttpStatus.NOT_FOUND;
UserNotFoundException unfe = (UserNotFoundException) ex;
return handleUserNotFoundException(unfe, headers, status, request);
} else if (ex instanceof ContentNotAllowedException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
ContentNotAllowedException cnae = (ContentNotAllowedException) ex;
return handleContentNotAllowedException(cnae, headers, status, request);
} else {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleExceptionInternal(ex, null, headers, status, request);
}
}
}
#ControllerAdvice
public class GlobalExceptionHandler{
#ExceptionHandler({ UserNotFoundException.class, ContentNotAllowedException.class })
public final ResponseEntity<ApiError> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof UserNotFoundException) {
HttpStatus status = HttpStatus.NOT_FOUND;
UserNotFoundException unfe = (UserNotFoundException) ex;
return handleUserNotFoundException(unfe, headers, status, request);
} else if (ex instanceof ContentNotAllowedException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
ContentNotAllowedException cnae = (ContentNotAllowedException) ex;
return handleContentNotAllowedException(cnae, headers, status, request);
} else {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleExceptionInternal(ex, null, headers, status, request);
}
}
}
ResponseEntityExceptionHandler is used when one is generally satisfied with Spring's default ExceptionHandlers - except for a few, which may then be overridden.
Have a look at all the protected methods in the API documentation: ResponseEntityExceptionHandler
Your GlobalExceptionHandler is already accepting any Exceptions and custom handling two specific Exceptions.
If you insist on using ResponseEntityExceptionHandler, a similar effect may be achieved by extending the class and implementing handleExceptionInternal():
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public handleExceptionInternal() {
...
}
}

#ControllerAdvice not throwing exception

I am handling exceptions globally in CustomHandler class. I could see while debugging this class is catching exceptions, but not throwing them to client. Please let me know what i am missing...
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse("Server Error", details);
return new ResponseEntity<Object>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(ConstraintViolationException.class)
public final ResponseEntity<Object> constraintValidationException(
ConstraintViolationException e) {
List<String> details = new ArrayList<>();
for (ConstraintViolation violation : e.getConstraintViolations()) {
details.add(violation.getMessage());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity<Object>(error, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> details = new ArrayList<>();
for(ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.UNPROCESSABLE_ENTITY);
}
Create your own application specific exceptions and handle them. E.g: public class MyAppEx extends RuntimeException {}. Catch the Exception.class and then throw your exception:
try {
// something goes wrong
} catch (Exception e) {
throw new MyAppEx("details of the exception");
}
Replace:
#ExceptionHandler(MyAppEx.class)
public final ResponseEntity<Object> handleAllExceptions(MyAppEx ex, WebRequest request) {..}

Spring missing query parameters exception handling

I have this code:
#GetMapping(value = "/users/{id}")
#ResponseStatus(HttpStatus.OK)
public DtoUser getUserById( #PathParam("id") #PathVariable("id") #RequestParam Long id) {
return adminService.getUserById(id);
}
and this code:
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return error_with_my_info;
}
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {{
return error_with_my_info;
}
...
}
The problem is that when I send a request WITHOUT a parameter, it is handleHttpMessageNotReadable that is called, not handleMissingServletRequestParameter.
Why is that?
Can other API endpoints affect this behaviour, like having a PUT request handler with the same endpoint?
How can I make it so that handleMissingServletRequestParameter?
Improvised :
#GetMapping(value = "/users")
#ResponseStatus(HttpStatus.OK)
public DtoUser getUserById( #RequestParam(value="id" , required=true)Long id) {
return adminService.getUserById(id);
}
localhost:8080?id=test
now if you dont pass id it will give you handleMissingServletRequestParameter.

How to get request's URI from WebRequest in Spring?

I am handling REST exceptions using #ControllerAdvice and ResponseEntityExceptionHandler in a spring Rest webservice. So far everything was working fine until I decided to add the URI path(for which exception has occurred) into the BAD_REQUEST response.
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
logger.info(request.toString());
return handleExceptionInternal(ex, errorMessage(HttpStatus.BAD_REQUEST, ex, request), headers, HttpStatus.BAD_REQUEST, request);
}
private ApiError errorMessage(HttpStatus httpStatus, Exception ex, WebRequest request) {
final String message = ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage();
final String developerMessage = ex.getCause() == null ? ex.toString() : ex.getCause().getMessage();
return new ApiError(httpStatus.value(), message, developerMessage, System.currentTimeMillis(), request.getDescription(false));
}
ApiError is just a Pojo class:
public class ApiError {
private Long timeStamp;
private int status;
private String message;
private String developerMessage;
private String path;
}
But WebRequest has not given any api to get the path for which the request failed. I tried:
request.toString() returns -> ServletWebRequest: uri=/signup;client=0:0:0:0:0:0:0:1
request.getDescription(false) returns -> uri=/signup
getDescription is pretty close to the requirement, but doesn't meet it. Is there any way to get only the uri part?
Found the solution. Casting WebRequest to ServletWebRequest solved the purpose.
((ServletWebRequest)request).getRequest().getRequestURI().toString()
returns the complete path - http://localhost:8080/signup
There are multiple solutions to this problem.
1) One can get request URI and client information from WebRequest using
webRequest.getDescription(true).
true will show user's information such as client id and false will just print URI.
2) Instead of WebRequest of Use HttpServletRequest directly in method definition as
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request, HttpServletRequest httpRequest) {
logger.info(httpRequest.getRequestURI());
return handleExceptionInternal(ex, errorMessage(HttpStatus.BAD_REQUEST, ex, request), headers, HttpStatus.BAD_REQUEST, request);
}
Access the attribute of WebRequest object:
Object obj = webRequest.getAttribute("org.springframework.web.util.UrlPathHelper.PATH", 0)
String uri = String.valueOf(obj);
webRequest.getAttribute(String attributeName, int scope);
// scope can be either:
// 0: request
// 1: session
// valid attribute names can be fetched with call:
String[] attributeNames = webRequest.getAttributeNames(0); //scope is request
Valid attribute names are:
org.springframework.web.util.UrlPathHelper.PATH
org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER
org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
org.springframework.web.servlet.DispatcherServlet.CONTEXT
org.springframework.web.servlet.resource.ResourceUrlProvider
characterEncodingFilter.FILTERED
org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR
org.springframework.web.servlet.DispatcherServlet.THEME_SOURCE
org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER
formContentFilter.FILTERED
org.springframework.web.servlet.HandlerMapping.bestMatchingPattern
requestContextFilter.FILTERED
org.springframework.web.servlet.DispatcherServlet.OUTPUT_FLASH_MAP
org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping
org.springframework.web.servlet.DispatcherServlet.FLASH_MAP_MANAGER
org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
org.springframework.web.servlet.DispatcherServlet.THEME_RESOLVER
org.springframework.core.convert.ConversionService
ResponseEntityExceptionHandler explains A convenient base class for #ControllerAdvice classes that wish to provide centralized exception handling across all #RequestMapping methods through #ExceptionHandler methods. here
In Spring Boot 2.1.6, You can write as below:
RestExceptionHandler.java
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(RestExceptionHandler.class);
#ExceptionHandler(ResourceNotFoundException.class)
protected ResponseEntity<Object> handleEntityNotFound(ResourceNotFoundException ex, final HttpServletRequest httpServletRequest) {
ApiError apiError = new ApiError(HttpStatus.NOT_FOUND);
apiError.setMessage("Resource not found");
apiError.setDebugMessage(ex.getMessage());
apiError.setPath(httpServletRequest.getRequestURI());
return buildResponseEntity(apiError);
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ApiError apiError = new ApiError(HttpStatus.METHOD_NOT_ALLOWED);
apiError.setMessage(ex.getMessage());
apiError.setPath(((ServletWebRequest)request).getRequest().getRequestURI().toString());
logger.warn(ex.getMessage());
return buildResponseEntity(apiError);
}
}
Let's start by implementing a simple structure for sending errors:
ApiError.java
public class ApiError {
// 4xx and 5xx
private HttpStatus status;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private LocalDateTime timestamp;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
#JsonInclude(value = Include.NON_EMPTY)
private String debugMessage;
// returns the part of this request's URL
private String path;
#JsonInclude(value = Include.NON_EMPTY)
private List<String> details=new ArrayList<>();
// setters & getters
}
ResourceNotFoundException.java
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(String msg) {
super(msg);
}
I am using SpringBoot 2.5.3 and globalExceptionHandler.
short snipit. used "TheCoder" answer and went from there.
You do not have to use header, status, ... WebRequest as input args if you don't need them. This gives just the endpoint of the url and not hostname.
#ExceptionHandler(value = NotFound.class)
ResponseEntity<...> httpNotFoundException(NotFound exc, HttpServletRequest req ) {
//use req.getRequestURI();
}
#ExceptionHandler(value = HttpClientErrorException.class)
ResponseEntity<...> httpClientException(HttpClientErrorException exc, HttpServletRequest req ) {
exc.getRawStatusCode() //to get status code
//I am using this to check for 404 and handling here with other stuff instead of using NotFound.class above.
// Use req.getRequestURI();
}
You could use request.getDescription(false).

Spring validation returns long error messages, not just the customized message

Spring validation returns long error message instead of the customized once.
This is the section of code in the dto.
public class RequestDto implements Serializable {
#NotNull(message="{id.required}")
private Long id;
}
In controller added the #Valid for input.
#RequestMapping(value = ApiPath.PATH, method = RequestMethod.POST, produces = { "application/xml",
"application/json" })
public #ResponseBody ResultDecorator saveRequest(
#Valid #RequestBody RequestDto msaDisabScreenRequestDto) throws Exception {
}
API returns the following error.
<message>Validation failed for argument at index 0 in method: public om.gov.moh.msa.framework.resolver.ResultDecorator om.controller.MaController.saveRequest(om..dto.RequestDto) throws java.lang.Exception, with 1 error(s): [Field error in object 'requestDto' on field 'id': rejected value [null]; codes [NotNull.requestDto.id,NotNull.id,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestDto.id,id]; arguments []; default message [civilId]]; **default message [ID is required.]]** </message>
Here the custom message is present at the end. (default message [ID is required.)
Using Controller advice for global exception and I'm overriding handleMethodArgumentNotValid. How can I return only the custom message here?
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
apiError.setMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
}
You can get default/custom message like result.getFieldError("yourFieldName").getDefaultMessage()
You can catch error messages either through controller method which should look like this
#RequestMapping(value = ApiPath.PATH, method = RequestMethod.POST, produces = { "application/xml", "application/json" })
public #ResponseBody ResultDecorator saveRequest(#Valid #RequestBody RequestDto msaDisabScreenRequestDto, BindingResult result) throws Exception {
if(result.hasErrors()){
String errorMessage = result.getFieldError("yourFieldName").getDefaultMessage();
}
}
Or through Global Exception handler
Updated
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
//New Code
BindingResult bindingResult = ex.getBindingResult();
String errorMessage = result.getFieldError("yourFieldName").getDefaultMessage();
//---------------
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
apiError.setMessage(errorMessage);
return buildResponseEntity(apiError);
}
}
Thanks Afridi,
Created a string buffer and added all the error messages into that.
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
final StringBuffer errors = new StringBuffer();
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.append("\n");
errors.append(error.getField() + ": " + error.getDefaultMessage());
}
apiError.setMessage(errors.toString());
return buildResponseEntity(apiError);
}
As Afridi said in #ControllerAdvice can do this also:
#ExceptionHandler(value = MethodArgumentNotValidException.class)
#SuppressWarnings("unchecked")
#ResponseBody
public Result methodArgumentNotValidExceptionHandler(HttpServletRequest req, HttpServletResponse response, MethodArgumentNotValidException e) throws IOException {
String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
// todo return to your custom result
}
There are two point :
Exception class is MethodArgumentNotValidException
The first Error getDefaultMessage() can get your custom message in Annotation
In WebFlux :
Handle WebExchangeBindException for customising the default error message of #Valid
#ControllerAdvice
public class ValidationHandler {
#ExceptionHandler(WebExchangeBindException.class)
public ResponseEntity<List<String>> handleException(WebExchangeBindException e) {
var errors = e.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
}
Reference : https://www.vinsguru.com/spring-webflux-validation/

Resources