#Valid working with #ModelAttribute, not with #RequestAttribute - spring-boot

I'm implementing a #RestController and I realized that #Valid is working with #RequestBody or #ModelAttribute params of a #GetMapping method, but not with a #RequestAttribute parameter.
To get validated the #RequestAttribute annotated param I have to annotate my Controller class with #Validated.
Following my code:
Controller
#Log4j2
#RestController
#RequestMapping("/test/api/v1/entity")
public class MyController extends SomeController {
#GetMapping("/getInfo")
public ResponseEntity<<MyResponse>> infoStatus (RequestParam(required = false) String inputStr,
#Valid #RequestAttribute ObjectToValidate objToValidate){
//Any stuff here
}
}
Bean to validate
#Getter
#Setter
#Valid
public class ObjectToValidate {
#NotNull
#NotEmpty
private String anyCode;
}
The result is anyCode is not checked to be not null nor empty.
If I annotate MyController with #Validate, the ObjectToValidate param is validate as expected.
If I change controller as follows, the validation also works.
#Log4j2
#RestController
#RequestMapping("/test/api/v1/entity")
public class MyController extends SomeController {
#ModelAttribute
public ObjectToValidate addToModel(#RequestAttribute ObjectToValidate
objToValidate) { return objToValidate; }
#GetMapping("/getInfo")
public ResponseEntity<MyResponse> infoStatus (
#RequestParam(required = false) String inputStr,
#Valid #ModelAttribute ObjectToValidate objToValidate
){
//Any stuff here
}
}
Please, could you explain why?

#Valid can be used on #RequestBody Controller Method Arguments.
That is #RequestBody method argument can be annotated with #Valid to invoke automatic validation.
It will be no use if you annotate ObjectToValidate class with #Valid.
#PostMapping("/notes")
Note getNote(#Valid #RequestBody Note note) {
return repository.save(note);
}
To validate the path variable, the controller class should be annotated with #Validated
#RestController
#Validated // class level
public class NoteController {
#GetMapping("/note/{id}")
Note findOne(#PathVariable #NotBlank(message = "Id must not be empty") String id) {
return repository.findById(id)
.orElseThrow(() -> new NotekNotFoundException(id));
}
}
Hope it helps!!

Related

It seems `#GetMapping` doesn't work with `#Controller`, why is that?

Per the doc, #RestController is just a convenience annotation which combines #Controller and #ResponseBody and #GetMapping is a composed annotation that acts as a shortcut for #RequestMapping(method = RequestMethod.GET), which means #GetMapping should work well with both #RestController and #Controller
In fact #GetMapping only works with #RestController.
#RestController
public class HelloController {
#GetMapping("/")
public String hello(){
return "hello";
}
}
while
#Controller
public class HelloController {
#GetMapping("/")
public String hello(){
return "hello";
}
}
doesn't work.
I know I can use #RequestMapping(value = "/", method = RequestMethod.GET) instead, I'd just like to know why. Could someone give a clue?
#Controller
public class TestController {
#GetMapping("/")
#ResponseBody
public String hello() {
return "hello";
}
}
Add the #ResponceBody annotation then, it will work.
The #Controller annotation indicates that the class is a “Controller”
e.g. a web controller
The #RestController annotation indicates
that the class is a controller where #RequestMapping methods assume
#ResponseBody semantics by default i.e. servicing REST API.

How to validate Spring #PathVariable properties first?

I have the following PUT method in my Spring Controller class. There is a parameter annotated with #PathVariable. There is another parameter annotated with #RequestBody #Valid. The problem is RequestBody is getting validated first and the method returns in case of exception. But I want #Pathvariable to be validated first and return in case of exception.
#RestController
#Validated
#RequestMapping(value = "/v1/order", produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderMappingController {
#PutMapping(value = "/{id}/order-mapping", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> UpdateOrderMapping(#PathVariable(value = "id") #NotBlank(message = "InvalidID")
#ValidOrderId String id,
#RequestBody #Valid OrderMappingRequest request) {
//...
}
}
Thanks in Advance!
You can write custom annotation and validator to validate the #Pathvariable
#RestController
#Validated
#RequestMapping(value = "/v1/order", produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderMappingController {
#PutMapping(value = "/{id}/order-mapping", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> UpdateOrderMapping(#PathVariable(value = "id") #OrderId String id,
#RequestBody #Valid OrderMappingRequest request) {
//...
}
}
Create custom annotation OrderId which can validate with custom validator class OrderIdValidator
#Constraint(validatedBy = OrderIdValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.METHOD,PARAMETER })
public #interface OrderId{
public String message() default "order id not found ";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default{};
}
Then you can write the custom validator class which can check the business logic if the id is present in db or any other validation
public class OrderValidator implements ConstraintValidator<OrderId, String> {
#Autowired
private OrderRepository repository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return (repository.existsByPeriod(value));
}
}
I suggest to try a different approach. Try not to annotate #Valid on your OrderMappingRequest, then implement a custom org.springframework.validation.Validator for your OrderMappingRequest bean which will then be injected and invoke in your OrderMappingController#UpdateOrderMapping method.
public class OrderMappingController {
#Autowired
private OrderMappingRequestValidator validator
#PutMapping("/{id}/order-mapping")
public ResponseEntity<?> UpdateOrderMapping(#PathVariable
#NotBlank(message = "InvalidID")
#ValidOrderId String id,
#RequestBody OrderMappingRequest request,
BindingResult result) {
validator.validate(request, result);
if(result.hasErrors()){
... do stuff or throw
}
}
}

Add Custom Validation Annotation for a Parameter in Controller - JSR-303

I can't figure out how to resolve the following use case in Spring Boot. Indeed, I have a Spring Boot Rest Api (eg: user-api) with the following controller method with a custom validator for a parameter :
#PostMapping
public User createUser(#ValidZipCode #RequestBody #Valid User user){
return userService.saveUser(user);
}
The User Class is defined in an external dependency (eg: user-model). It has the following fields :
public class User {
#NotNull
private String firstName;
#NotNull
private String lastName;
private String zipCode;
// getters, setters ..
}
In, user-api I created the following custom annotation :
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ZipCodeValidator.class)
public #interface ValidZipCode {
String message() default "Must be a valid zipCode. Found: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And so the ZipCodeValidator implementation :
public class ZipCodeValidator implements ConstraintValidator<ValidZipCode, User> {
private ZipCodeService zipCodeService;
#Override
public void initialize(ValidZipCode constraintAnnotation) { }
#Override
public boolean isValid(User user, ConstraintValidatorContext constraintValidatorContext) {
return !Objects.isNull(user.getZipCode()) ?
zipCodeService.isValidZipCode(user.getZipCode()) :
false;
}
NB: zipCodeService.isValidZipCode() is a simple boolean method.
The problem is that when I call the endpoint it never access the #ValidZipCode annotation. Is there any bean configuration to set up to make it works ?
Thks for your help ;)
UPDATE
Thanks to #cassiomolin for his answer. Indeed, when I annotate the controller class with #Validated It works :D
I Hope this post will help other devs ;)
Ensure that your controller class is annotated with #Validated.
See the following quote from the documentation:
To be eligible for Spring-driven method validation, all target classes need to be annotated with Spring’s #Validated annotation [...]

How to have access to the POJO returned by a RestController method (endpoint) in Spring Boot?

I want to be able to modify the POJO returned by a RestController method (or endpoint) before it gets serialized into the HttpServletResponse as a stream of data. But I want to be able to do it outside the controller method code (as a middleware).
I have tried to do it using a HandlerInterceptor but I do not have access there to the POJO. I have also tried using AOP but the Pointcut was never called.
#RestController
public class TestController {
#GetMapping("/test")
public Resource<User> getTest() {
Resource<User> resource = new Resource<>();
resource.setData(new User("test user"));
return resource;
}
#Builder
#Getter
#Setter
#AllArgsConstructor
static class User {
private String username;
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
static class Resource<T> {
private T data;
private Set<String> errors;
}
}
I want to be able to add a list of errors (if needed) to the Resource returned by the Controller after the controller performs its own logic and returns.
To change the object after returned from #RestController method but before it is written to the HTTP response, you can implement ResponseBodyAdvice and declared it as #ControllerAdvice bean:
#ControllerAdvice
public static class Foo implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//body is the object return from the #RestController method
//Cast it and modify it accordingly.
if(body instanceof Resource) {
Resource res = (Resource)body;
//Modify it .... blablablba
}
return body;
}
}

Abstract class and url mapping

I have an abstract class
I try to have all generic method in this class.
I get issue about mapping.
public abstract class BaseControllerNew<T extends BaseEntity, R extends BaseDto, S extends BaseSearch> {
...
#GetMapping(value = "/{id}")
public R getById(#PathVariable("id") Integer id){
return baseServiceNew.getById(id);
}
#GetMapping(value = "/")
public Page<R> get(Pageable page){
return baseServiceNew.get(page);
}
....
}
#RequestMapping(value = "/rest/vehicules")
#RestController
public class VehiculesRestController extends BaseControllerNew<Vehicules, VehiculesDto, VehiculesSearch>{
private VehiculesServiceImpl vehiculesService;
#Autowired
public VehiculesRestController(final VehiculesServiceImpl vehiculesService) {
super(vehiculesService);
this.vehiculesService = vehiculesService;
}
I'm able to call
/rest/vehicules/1
but i get 404 for
/rest/vehicules
The problem is with your additional "/", this means your URL will be "/rest/vehicules/"
You only need #GetMapping
#GetMapping
public Page<R> get(Pageable page){
return baseServiceNew.get(page);
}

Resources