Spring Boot Validation with ControllerAdvice Not Behaving Deterministically - java

I have the following setup
#ControllerAdvice
public class AppControllerAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler({UserInputValidationException.class})
public ResponseEntity<UserInputValidationResponseBody> handleBadInputException(UserInputValidationException ex, WebRequest request) {
return new ResponseEntity<>(
new UserInputValidationResponseBody().setFieldErrors(ex.getFieldErrors()),
HttpStatus.BAD_REQUEST
);
}
}
This is roughly the #RestController that throws well formatted validation exceptions
#RestController
#RequestMapping("api")
public class MyController {
/**
per the answer, BindingResult must immediately follow the #RequestBody or the item being found
*/
#PostMapping
public ResponseEntity<?> foo(#Valid #RequestBody FormPOJO formBody, Principal principal, BindingResult bindingResult) {
// if bindingResult has errors, throw a UserInputValidationException
}
}
And POJOs that I want to bind have JSR-303 validation annotations on them, Spring correctly validate them at Bind time during request parameter binding
However ... while I got this setup to work for a while - then Spring randomly started to bypass the #RestController and #ControllerAdvice
It appears that now I am receiving org.springframework.web.bind.MethodArgumentNotValidException ... i.e. the request is getting short circuited
I am running Spring Boot 1.5.4.RELEASE ...
EDIT following suggestion from another thread, I added
#Order(Ordered.HIGHEST_PRECEDENCE)
to the controller advice ... it only served to make matters worse. now there is absolutely no validation errors - the client only receives a blank message (which was the symptom of the problem for a while before the current issue surfaced without any code changes)

Ok it turns out
An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the #RequestBody or the #RequestPart arguments to which they apply: public org.springframework.http.ResponseEntity com.remo.api.portfolios.PortfolioController.put(java.security.Principal,org.springframework.validation.BindingResult,com.remo.api.portfolios.Portfolio
tl;dr Please go ahead and make sure #RequestBody is declared IMMEDIATELY before BindingResult

Related

Spring Boot - Rest Api occasionally adds Error Object to a response

I use spring-boot-starter-web (Spring Boot 2.3.0.RELEASE) and my Controller looks like:
#RestController
#RequestMapping(value = "/v1")
public class Controller {
#GetMapping
public ResponseEntity<Object> getObject() {
return ResponseEntity.ok(new Object());
}
}
I've noticed that sometimes in a browser I catch a response like
{}{"timestamp":"2021-03-11T13:39:08.100+00:00","status":200,"error":"OK","message":"","path":"/v1"}
This reproduces randomly - most time I get a correct JSON {}.
Could you advise how to disable adding of the second JSON and what can be a reason for this behavior? Is it a bug?

Conflict in Using #RepositoryRestController and #RepositoryRestResource For a Single Resource in Spring Boot

I have a Payment entity in my spring boot application. Considering all possible CRUD operations, I'm using spring data rest for read and want to implement a custom create operation. Also delete and update are not allowed for this entity.
So this is my desired URLs and resoponsible component for each one:
GET /payments : PaymentRepository
GET /payments/{id} : PaymentRepository
POST /payments : PaymentController
This is my repository:
#RepositoryRestResource
public interface PaymentRepository extends PagingAndSortingRepository<Payment, Long> {
// disable create and update
#Override
#RestResource(exported = false)
Payment save(Payment entity);
// disable delete
#Override
#RestResource(exported = false)
void delete(Payment entity);
}
And this is my controller:
#RepositoryRestController
#RequestMapping("/payments")
public class PaymentController {
#PostMapping("")
#ResponseBody
public Payment create() {
// some code...
}
}
If I map create operation to a url like POST /payments/create, everything works fine, but If I use the above code and map create to POST /payments, the GET /payments url does not work any more and I get 405 Method Not Allowed error. (GET /payments/{id} is still working)
It seems in this case presence of #PostMapping("") annotation, cause the PaymentController to responsd the GET /payments request and it fails.
I hope my explanations were clear. How can I solve this problem?
The Spring Data REST reference states that:
Sometimes you may want to write a custom handler for a specific resource. To take advantage of Spring Data REST’s settings, message converters, exception handling, and more, use the #RepositoryRestController annotation instead of a standard Spring MVC #Controller or #RestController.
It is not explicitly mentionned, but annotating your controller with #RepositoryRestController also allows you to define a custom behavior for one endpoint while keeping all the other endpoints that Spring automatically generates... On one condition: the #RequestMapping annotation can only be used at the method level (this is actually what is done in the example of the reference documentation).
Your example becomes:
#RepositoryRestController
public class PaymentController {
#PostMapping("/payments")
#ResponseBody
public Payment create() {
// some code...
}
}
With this, you get your custom endpoint mapped to POST /payments requests, plus all endpoints automatically generated by Spring, minus the ones annotated with #RestResource(exported = false).
#BasePathAwareController
#RepositoryRestController
public class PaymentController {
#PostMapping("/payments")
#ResponseBody
public Payment create() {
// some code...
}
}
You should modify your controller in the above way. #BasePathAwareController enables the custom REST URI's to get registered under your base URI.
With the above modification : both API's can work fine.

Returning JSON with spring on 404 in XML free project

I am currently setting up a Spring MVC application (version 4.1.4.RELEASE) and I want the application to return a JSON string on a 404 error rather than the default html response. I am using Tomcat 8 as my server. I have what I think should be correct, however it isn't behaving in the manner that I expect. What I'm trying to do is based off of this answer.
public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
...
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration){
registration.setInitParameter("throwExceptionIfNoHandlerFound","true");
}
}
and then I have an exception controller (which is different than the question I based my solution off of, however I don't believe that is an issue as I am under the impression that #ControllerAdvice is an acceptable way to manage this based off of the Spring Docs. It looks something like:
#ControllerAdvice
public class GlobalExceptionController{
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Message handleMethodNotSupported(HttpServletRequest request){
...
}
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public Message handleBadRequest(HttpServletRequest request){
...
}
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public Message requestHandlingNoHandlerFound(HttpServletRequest request){
...
}
...
}
It continues to send back the default response. I know for a fact that it is hitting my customizeRegistration() function because breakpoints stop it, however, any breakpoints that I have in my GlobalException class are not hit. Also, the GlobalException class is within a package that is hit by a #ComponentScan() annotation, so I am fairly confident that it is also being handled by spring.
I assume I'm missing something obvious, any help would be greatly appreciated.
I don't think the return type you're trying to use is supported. Have you tried changing your return value to ResponseEntity or adding a #ResponseBody annotation?
From the docs:
A ModelAndView object (Servlet MVC or Portlet MVC).
A Model object, with the view name implicitly determined through a RequestToViewNameTranslator.
A Map object for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator.
A View object.
A String value which is interpreted as view name.
#ResponseBody annotated methods (Servlet-only) to set the response content. The return value will be converted to the response stream
using message converters.
An HttpEntity or ResponseEntity object (Servlet-only) to set response headers and content. The ResponseEntity body will be
converted and written to the response stream using message converters.
void if the method handles the response itself (by writing the response content directly, declaring an argument of type
ServletResponse / HttpServletResponse / RenderResponse for that
purpose) or if the view name is supposed to be implicitly determined
through a RequestToViewNameTranslator (not declaring a response
argument in the handler method signature; only applicable in a Servlet
environment).

Spring MVC #Valid Validation with custom HandlerMethodArgumentResolver

I want to register a custom HandlerMethodArgumentResolver that could handle the following #Controller handler method definition
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
I can register my resolver, which just creates a Domain object through request parameters, by overriding addArgumentResolver() from WebMvcConfigurerAdapter. When Spring tries to resolve the Domain parameter, it goes through its list of HandlerMethodArgumentResolver (there are a lot) and picks the first one that supports() it.
In the above example, although my resolver will get called and my Domain argument will get initialized, the #Valid annotation won't have been processed and the resolver for BindingResult, an ErrorsMethodArgumentResolver will fail because it requires a #ModelAttribute, #RequestBody or the #RequestPart argument in the handler method, which I don't have.
If I try to fix it by adding #ModelAttribute
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid #ModelAttribute Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
a HandlerMethodArgumentResolver implementation, ModelAttributeMethodProcessor, will get checked first with supports() and resolve the argument (with #ModelAttribute and #Valid) before my custom resolver. The BindingResult won't fail, but I won't have my custom creation behavior on the Domain instance.
I could just copy-paste the code for validation and adding to model that's in ModelAttributeMethodProcessor, but I was hoping there was an easier way to resolve my parameters and perform validation without adding an object to the model. Is there such a way?
Nice description of the issue that you are facing.
I checked out the code that you have outlined and have come to the same conclusion that you have - there is no built-in way to have both a custom HandlerMethodArgumentResolver as well as #Valid related validation applied at the same time, the only choice is to do what the ModelAttributeMethodProcessor does which is to check if the parameter has a #Valid annotation and call the validation logic related code.
You can probably derive your HandlerMethodResolverArgumentResolver from ModelAttributeMethodProcessor and call super.validateIfApplicable(..) atleast this way the existing code is leveraged.
It's may be too late, but your HandlerMethodArgumentResolver gets WebDataBinderFactory object as last argument, then, to hook up the validation, simply add this to your resolver implementation:
Object resolvedObject = // your logic
if(parameter.hasParameterAnnotation(Valid.class){
binderFactory.createBinder(webRequest,resolvedObject,"resolvedObjectLogicalName").validate ();
}

Data Binding Error Handling in Spring MVC

I have a question about data binding in Spring MVC.
I have a Controller which accepts a JSON request in the form of #RequestBody. I have all the JSR 303 validations in place and it works like a charm.
JSON Request
public class TestJSONRequest {
#Size(min=10,message="{invalid.demo.size}")
String demo;
int code;
}
Controller
#Controller
#RequestMapping("/test")
public class TestController {
public void testEntry(#RequestBody TestJSONRequest jsonRequest,ModelMap map)
Set<ConstraintViolation<TestJSONRequest>> violationList = validator.val(jsonRequest);
....
....
TestJSONResponse response = // Do complex Logic.
modelMap.addattribute("TestJSONResponse",response);
}
}
But JSR 303 validations kick in once the incoming JSON data is bound to the Request object.
If I send ab in the code field of the input JSON request, binding would itself fail.
How do I handle that?
I want to catch those data binding errors and do some kind of generalized error handling in my controller.
Could you please help me out on this?
P.S - I am using Spring 3.0.3
According to the current Spring documentation (V3.1) :
Unlike #ModelAttribute parameters, for which a BindingResult can be used to examine the errors, #RequestBody validation errors always result in a MethodArgumentNotValidException being raised. The exception is handled in the DefaultHandlerExceptionResolver, which sends a 400 error back to the client.
Now you can to tell Spring that you'd like to handle this, by creating a new method, as follows:
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleValidation(MethodArgumentNotValidException e, ModelMap map) {
List<ObjectError> errors = e.getBindingResult() .getAllErrors();
// your code here...
return "path/to/your/view";
}
Finally, have a read of the Spring docs wrt #ExceptionHandler. There's most likely some useful information there.

Categories