Spring Boot - how to handle UnrecognizedPropertyException from Jackson manually? - java

So I have a controller that takes input a request body:
#RestController
#RequestMapping("/foo/bar")
public class FooBarController {
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> doTheFoo(#RequestBody MyDto dto) {
...
}
}
And I have this FooBar:
public clas FooBar {
#JsonProperty("foo")
private String foo;
public void setFoo(String foo) { ... }
public String getFoo() { ... }
}
And I have set in my properties to fail on unknown properties:
spring.jackson.deserialization.fail-on-unknown-properties: true
And I also have a handler to try and capture the failure:
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(UnrecognizedPropertyException.class)
public ResponseEntity<?> handleUnrecognizedPropertyException(UnrecognizedPropertyException ex) {
...
}
}
But when I POST to my endpoint with an unknown property (e.g. {"bar": "baz"}), nothing in the GlobalExceptionHandler is run and instead a vanilla 400 BAD REQUEST is returned. How do I intercept the unknown property exception and provide a different response?

if you are using properly the property and your controller works well, you could intercept the HttpMessageNotReadableException exception.

Try BasicErrorController of Spring Boot. This should look something like this.
#Controller
#RequestMapping
public class ErrorController {
#RequestMapping( value = "/error/404.html" )
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public String pageNotFoundError( HttpServletRequest request ) {
"errors/404"
}

Related

ResponseEntityExceptionHandler in SpringBoot 2.0.4.RELEASE

I have a basic SpringBoot 2.0.4.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have created this class to manage the exceptions
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public RestResponseEntityExceptionHandler() {
super();
}
// API
// 400
...
}
But when I try to use it in 1 of my method :
#GetMapping(path = "/getUsers", consumes = "application/json", produces = "application/json")
#ExceptionHandler({RestResponseEntityExceptionHandler.class })
public ResponseEntity<List<User>> testErrors(HttpServletRequest request, #RequestHeader(value = "Authorization") String authHeader) {
...
}
I got this compilation error :
Type mismatch: cannot convert from Class to Class>
If you read the docs for #ExceptionHandler you will see that it is used to mark a method to handle exception(s). Therefore you have to specify which exception(s) it can handle (e.g. #ExceptionHandler(MyException.class) or #ExceptionHandler({MyException.class, MyOtherException.class})). When such an exception occurs in your controller this method gets invoked.
#ControllerAdvice makes the methods defined in the class available to all your controllers.
I don't know what you want to do, but since you extend ResponseEntityExceptionHandler in the #ControllerAdvice class, you could just delete the #ExceptionHandler annotation:
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
...
}
If you want to handle specific exceptions on your own, you can do it like this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleMyException() {
...
}
}
#Controller
public class MyController {
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
throw new MyException();
}
}
Now handleException() will be called when an error occurs in testErrors().
If you want to handle exceptions only in one controller you can do this:
#Controller
public class MyController {
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
throw new MyException();
}
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleMyException() {
...
}
}
It's expected for this not to work correctly.
You're attempting to pass in the RestResponseEntityExceptionHandler as the arguments of the #ExceptionHandler annotation in your test method. This is wrong, as this annotation accepts the type of the exception that gets intercepted.
Also in general it seems that the placement of your #ExceptionHandler annotation seems to be wrong. This are placed within method that reside in the actual exception handling class.
I suggest you have a good read on the way Spring handles exceptions in that manner.
Have a look at this piece of documentation: exception handling in Spring.

Transferring an object via REST

I've been trying to send an object from one application to another using rest.
Sender:
#Controller
public class Sender {
#RequestMapping(value = "/comMessageApp-api/getMessages")
public String restGetMessages() {
String url = "http://localhost:8079/comMessageApp-api/responseMessages";
HttpEntity<Dto2> entity = new HttpEntity<>(new Dto2());
ResponseEntity<Dto2> response = restTemplate.exchange(url, HttpMethod.POST, entity, Dto2.class);
}
}
Receiver:
#RestController
public class Receiver {
#RequestMapping(value = "/comMessageApp-api/responseMessages")
public void restResponseMessages(HttpEntity<Dto2> request) {
System.out.println(request.getBody());
}
}
DTO:
public class Dto2 {
private String string = "Test string";
public Dto2() {
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
Jackson is used serialization/deserialization.
Any ideas, why request.getBody() printed in the Receiver is null???
I tried to send the object inside HttpEntity and inside RequestEntity. No success in both cases. On the receiving side I always get null.
Your sender (client) side is very close but your server side doesn't return a value so change the type to Void:
ResponseEntity<Void> response = restOps.exchange(url, HttpMethod.POST, entity, Void.class);
Your receiver (server) side is not quite set up correctly either, you need to set the HTTP method to [edited] POST. You'll also need to tell Spring to map the body of the request (your rest payload) onto the parameter;
#RequestMapping(value = "/comMessageApp-api/responseMessages", method=RequestMethod.POST)
public void recieveDto (#RequestBody final Dto dto) {
System.out.println(dto.toString());
}
[EDIT] Brainfart, the http method should be set to POST on receive annotation.
[Further suggestion]
403 errors may be due to Spring Security, if you have it switched on (check your POM if you're not sure) try this;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests().
antMatchers("/**").permitAll();
}
}
You'll want to be tightening up security once you know it works.
try to use #RequestMapping(method = RequestMethod.POST, produces = "application/json", consumes = "application/json")

Validate Json Object before casting it to POJO - using #requestBody

I would like to validate the incoming json object in controller before casting it to POJO using spring jackson.
My Controller:
#RequestMapping( value = "/createContact" , method = RequestMethod.POST , consumes = MediaType.APPLICATION_JSON_VALUE , produces = MediaType.APPLICATION_JSON_VALUE )
public Contact createContact( #RequestBody Contact contact ) throws Exception
{
return ContactService.createContact( contact );
}
My Contact.java
public class Contact
{
private String ID = UUID.randomUUID().toString();
private String type = "contact";
private String category;
private String name;
}
What I am trying to achieve is that 'type' field should not be passed in the request json. I need to throw an exception if the consumer passes that value.
I can get the json as a Map or string and validate it and then cast it to POJO. But is it possible to validate it before direct casting?
This can be done with an interceptor which will extend HandlerInterceptor. For example, you can create a ContactRequestValidator class like below.
#Component("contactRequestInterceptor")
public class ContactRequestValidator implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
// get the request payload using reader from httpServletRequest and do the validation
// and throw an exception if not valid and may handle it using an Spring MVC exception handler
}
// other two methods omitted..
}
Then register the validator interceptor with
#Configuration
public class MVCConfigurerAdapter extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("contactRequestInterceptor")
private HandlerInterceptor contactRequestValidator;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(contactRequestValidator).addPathPatterns("/api/**"); // Also have the option to use Ant matchers
}
}

How to have two ControllerAdvice in the same SpringMvc application

I would like to manage Exception thrown by simple Controller or RestController in two ways:
1) html redirection
2) Json error
I tested the code below :
#ControllerAdvice(annotations = Controller.class)
public class ExceptionHandlerController
{
#ExceptionHandler(Exception.class)
public ModelAndView handleException(HttpServletRequest _req, Exception _ex)
{
K_LOGGER.info("test");
return new ModelAndView();
}
}
#ControllerAdvice(annotations = RestController.class)
public class ExceptionHandlerRestController
{
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(HttpServletRequest _req, Exception _ex)
{
return new ResponseEntity<>("test", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#RestController
public class GreetingController
{
#RequestMapping("/greetingexception")
public Greeting greetingException(#RequestParam(value = "name", defaultValue = "World") String name)
throws Exception
{
throw new Exception();
}
}
It doesn't work properly, I always pass by ExceptionHandlerController but not by ExceptionHandlerRestController.
I think it's because #RestController inherit of #Controller.
Do you have a other solution?
Try to add #Order(Ordered.HIGHEST_PRECEDENCE) annotation to rest exception handler. It may helps you.
eg04lt3r answer is correct, just though that more details might be useful for someone.
In case when you have global #ControllerAdvice and want to handle some exception in a different way in one of your Controllers you need to set #Order(Ordered.HIGHEST_PRECEDENCE) on the #ControllerAdvice which should have higher priority.
For example:
#ControllerAdvice
public class GeneralExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#ControllerAdvice(assignableTypes = MyController.class)
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#Order is needed because on startup one of the handlers will register with higher order automatically, anyway and your exception handling will become unpredictable. For example I recently saw a case when if you start an app using bootRun gradle task MyExceptionHandler was primary, but when started as jar GeneralExceptionHandler was primary.

Custom exception handle with spring boot

Here,my requirement is that i want separate code in my application for exception handling,i saw a nice option of spring there using #controller advice to handle exceptions globally.
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
But there i want to cutomization there,like proper dynamic messages,own error code. so how can i do this,i am new to spring boot and even i don't have knowledge of spring.Need basic example.
You can come up with a class like this to capture information to be sent in response in case of exception:-
public class APIResponse {
int errorCode;
String description;
String someInformation;
// any other information that you want to send back in case of exception.
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ResponseBody
#ExceptionHandler(DataIntegrityViolationException.class)
public APIResponse handleConflict(DataIntegrityViolationException exception) {
APIResponse response = createResponseFromException(exception);
return response;
}
}
In your controller advice class:-
Have the return type APIResponse instead of void.
The handler method can have the exception raised as the argument.
Using the exception object to create the APIResponse object.
Put #ResponseBody on the handler method.

Categories