Spring Boot validation throws BindException insted of MethodArgumentNotValid Exception - spring-boot

I switched to Spring Boot 3 and setup a simple project.
This contains input validation. I created a simple model and annotated the fields
#Data
#NoArgsConstructor
public class DemoUserCreateDto {
#Email(message = "email must be valid")
private String emailContentCreator;
#NotNull(message = "must not be null")
private String emailContentConsumer;
}
On my Controller level I have a simple endpoint:
#PostMapping(value = "/users", consumes = MediaType.APPLICATION_JSON_VALUE)
public String createUsers(#RequestBody #Valid DemoUserCreateDto demoUserCreateDto) {
return "DONE";
}
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
PROBLEM:
The validation is done on the #NotNull field. The #Email field does not have any effect.
import is:
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
The exception thrown on the NotNull validation is:
org.springframework.validation.BindException
but I was expecting a
MethodArgumentNotValidException
WHAT I'VE TRIED:
I tried to annotate my controller with #Validated. No success.
The thing is that I've set this up in previous projects with spring boot 2.x.
I've seen that in termins of validator libraries, jakarta is used instead of javax.
Has something changed in the implementation? I've also read that there is a change in the latest lombok version, where they force you to use the validation statement on the getter method of the attribute. I've reset to a minor version, but nothing changed.

Related

Spring boot2 path variable validation

I am using spring boot for creating rest services. I need to validate the parameter passed. I have a service like below,
#GetMapping(value="/employee/{Id}")
public EmployeeDTO getEmployeeDetails(#PathVariable String Id) {
...
}
I need to throw error if Id is not passed in url. Like "Missing Id in request". I was able to achieve using below,
#GetMapping(value={"/employee", "/employee/{Id}"})
public EmployeeDTO getEmployeeDetails(#PathVariable String Id) {
...
}
And handled MissingPathVariableException in ExceptionHandler annotated with #ControllerAdvise.
But I wanted to know is this the right way to check ?
You can use #ControllerAdvise to handle exceptions that are generated while executing your actual code.
For Path variable validation, you can make use of spring-boot-starter-validation.
Add this maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Then your controller will look like:
#GetMapping(value={"/employee", "/employee/{Id}"})
public EmployeeDTO getEmployeeDetails(
#NotBlank(message = "Missing Id in request")
#PathVariable String Id) {
...
}
I recommend you to read this: Validating Form Input

Spring 5 Webflux functional endpoints - How to perform input validation?

According to the current doc (5.0.0.RELEASE) Spring Webflux supports validation when working with annotated controllers:
By default if Bean Validation is present on the classpath — e.g.
Hibernate Validator, the LocalValidatorFactoryBean is registered as a
global Validator for use with #Valid and Validated on #Controller
method arguments.
However nothing is said about how to automate it with functional endpoints. In fact, the only example of input processing in the documentation doesn't validate anything:
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}
Are we supposed to do this manually or there is some automatic way to do it?
In Spring version 5.0, there is no automatic way to do validation in functional endpoints, and as such validation must be done manually.
Though there are currently no concrete plans to do so, we might add some sort of validation in the future. But even then it will be an explicit method call, and not an automatic mechanism. Overall, the functional endpoint model is designed to be a lot more explicit than the annotation-based model.
As arjen-poutsma said, it seems there is no way of running automated validations on Spring 5 functional endpoints.
Spring documentation is not very clear about this, and it doesn't suggest any approach.
On this Baeldung article, you'll find an idea on how you can run validations using this approach (disclaimer: I'm the writer of the article :) )
In a nutshell, you can follow these steps:
Implement Spring Validators to evaluate your resources
Create an abstract class with the basic procedure that any handler will follow when processing a request, leaving up to the children classes what to do when the data is valid
Make your request handler classes extend this abstract class, implementing this abstract method, stating the body it will be expecting, and what validator needs to be used to validate it
EDIT:
I've been following this related Spring issue, and it seems we now count with official documentation regarding this subject: https://github.com/spring-projects/spring-framework/blob/master/src/docs/asciidoc/web/webflux-functional.adoc#validation
The suggested approach is to use validators as explained in the article.
At the current version(2.0.4.RELEASE) there isn't a way to do automatic validation with handles, however you always could make a manual validation like this:
#Slf4j
#Component
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
#RequiredArgsConstructor
public class MyHandlerValidator implements HandlerValidator<MyResource> {
Validator validator;
#Override
public void callValidator(final MyResource fdr) {
final DataBinder binder = new DataBinder(fdr);
binder.setValidator(validator);
binder.validate();
if (binder.getBindingResult().hasErrors()) {
final String reason = binder.getBindingResult().getFieldError().toString();
log.error(reason);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason);
}
}
}
The thing with this, its that the you should throw a WebExchangeBindException like automatic validation does, however i could't create a MethodParameter witch is a dependency to create this exception.
UPDATE:
Spring show us a way to do it, which is similar to my solution, but, not enough in my opinion on documentation
Just to demo some working code. If you need simple validation based on the object annotations like:
#Value
#Builder
#Jacksonized
public class SigninRequest {
#NotBlank(message = "The username is mandatory")
#Email(message = "The username should be valid Email")
String username;
#NotBlank(message = "The password is mandatory")
String password;
}
At the handler you need just one simple additional operator doOnNext:
#Component
#RequiredArgsConstructor
public class AuthHandler {
private final AuthService authService;
private final ObjectValidator validator;
public Mono<ServerResponse> signin(ServerRequest request) {
return ok().body(
request.bodyToMono(SigninRequest.class)
.doOnNext(validator::validate) //<-- just one single line
.flatMap(login -> authService.authenticate(login.getUsername(), login.getPassword())),
AuthResult.class);
}
}
The ObjectValidator is doing actual validation and throws the runtime exception with the 4xx error in case of validation errors:
#Component
#RequiredArgsConstructor
public class ObjectValidator {
private final Validator validator;
public <T> T validate(T object) {
var errors = validator.validate(object);
if (errors.isEmpty()) {
return object;
} else {
String errorDetails = errors.stream().map(er -> er.getMessage()).collect(Collectors.joining(", "));
throw new ObjectValidationException(errorDetails);
}
}
}
And the exception:
#ResponseStatus(code = HttpStatus.UNPROCESSABLE_ENTITY)
public class ObjectValidationException extends RuntimeException {
public ObjectValidationException(String errorDetails) {
super("Please supply the valid data: " + errorDetails);
}
}
If you properly setup global error handling you can keep you handler code clean and reuse the object validator across all your handlers.

Best way how to describe "ModelAttribute" with Swagger

I am trying to integrate Swagger2 to my Spring Boot based application. The issue is that swagger does not consider model attributes.
#GetMapping(value = "/events", produces = MediaType.APPLICATION_JSON_VALUE)
public PagedResources<EventResource> getEvents(
#Valid SearchCriteria searchQuery,
BindingResult result,
PageableResourcesAssembler<EventResource> assembler){
// code
}
As you can see SearchCriteria is a class which gets automatically binded by Spring.
public class SearchCriteria {
private List<EventType> eventTypes;
private LocalDateTime from;
// getters setters
}
But what swagger generates is following:
which is not expected. The desired result might be generated by annotation getEvents method by
#ApiImplicitParams({
#ApiImplicitParam(name = "eventTypes", paramType = "query"),
#ApiImplicitParam(name = "from", paramType = "query")
})
PagedResources<EventResource> getEvents(#ApiParam(hidden = true ) #Valid SearchCriteria searchQuery
but the #ApiParam(hidden = true ) does not work, because in the Swagger UI is the searchQuery parameter still present.
What is the proper way how to describe request parameters contained in a POJO using swagger? To me the best way would by annotation SearchCriteria class with #ApiModel but it does not work.
This bug was fixed in Springfox v2.7.0.
Original Answer:
The #Valid-annotation actually does that the param will be seen as body-param.
As this shouldn't do this I've opened an issue on the springfox github page.
but the #ApiParam(hidden = true ) does not work
Springfox provides for that the springfox.documentation.annotations.ApiIgnore-annotation which should work.
Like written in this issue using the annotation from springfox is the right way.

JsonView for filtering Json properties in Spring MVC not working

In our Spring MVC web application for job recruiting, I work on a RESTful service to get information about available companies for a given account, or more detailed data for a single company.
This is implemented using Spring MVC in a pretty straightforward way.
Logically, the API for a single company shows more details than for a list of companies. In particular, there are two fields (CoverPhoto and Logo) whoch are only to be shown when querying the details for a single company by its id.
For the generation of the Json output, I use Jackson to annotate the returned DTO object for specific field names because sometimes they are different from the member variable names.
One of the ways to implement this in an elegant way is using JsonViews, as described in these tutorials:
https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
http://www.baeldung.com/jackson-json-view-annotation
The only difference between them is that the second one uses interfaces for the View classes, and the first one uses classes. But that should not make any difference and my code is not working as expected with either of them.
I have created to interfaces (ObjectList and OblectDetails) and annotated the fields in my DTO with
#JsonView(Views.ObjectList.class)
for the fields I want to see on both the lisdt and the details API, and with
#JsonView(Views.ObjectDetails.class)
for the fields only to shown in the single company API.
But unfortunately, both API's show all fields, regardless of the annotation. Also fields without a #JsonView annotation appear in the output JSON, while according to the documentation, when annotating the Controller method with a #JsonView, each fields should also be annotated with a #JsonView annotation to show up.
My simplified code looks as follows:
DTO:
package nl.xxxxxx.dto.too;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonView;
#JsonAutoDetect
#JsonPropertyOrder({"id", "name", "logo", "coverPhoto", "description", "shortDescription",
"phone", "address"})
public class XxxCompanyDto {
#JsonView(Views.ObjectList.class)
private Long id;
#JsonView(Views.ObjectList.class)
private String name;
#JsonView(Views.ObjectDetails.class)
private String logo;
#JsonView(Views.ObjectDetails.class)
#JsonProperty("cover_photo")
private String coverPhoto;
#JsonView(Views.ObjectList.class)
private String description;
//more fields
//setters, no getters are used to prevent ambiguity for Json generation
//omitted for clarity
}
Views:
package nl.xxx.dto.too;
public class Views {
public interface ObjectList {}
public interface ObjectDetails extends ObjectList {}
}
Controller:
package nl.xxx.controller;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.web.bind.annotation.*;
//more imports
/**
* Created by Klaas van Gelder on 17-Nov-16.
*/
#RestController
public class XxxCompanyController {
#Autowired
//services omitted
#JsonView(Views.ObjectDetails.class)
#RequestMapping(value = "/public-api/xxx/company/{companyId}", method = RequestMethod.GET)
public TooCompanyDto getCompanyById(
#RequestHeader(value = "X-Channel") String publicationChannelToken,
#PathVariable(value = "companyId") Long companyId) {
XxxCompany tooCompany = tooCompanyService.getCompanyById(companyId);
//some verifications omitted
TooCompanyDto tooCompanyDto = tooCompanyJsonConverter.convertToDto(tooCompany);
return tooCompanyDto;
}
#JsonView(Views.ObjectList.class)
#RequestMapping(value = "/public-api/xxx/company", method = RequestMethod.GET)
public List<TooCompanyDto> listCompaniesForChannel(
#RequestHeader(value = "X-Channel") String publicationChannelToken) {
XxxPublicationChannel channel = tooVacancyService.findPublicationChannelByToken(publicationChannelToken);
List<XxxCompany> xxxCompaniesForChannel = xxxCompanyService.findCompaniesByPublicationChannelToken(publicationChannelToken);
List<XxxCompanyDto> dtoList = new ArrayList<>();
for (XxxCompany xxxCompany : xxxCompaniesForChannel) {
XxxCompanyDto xxxCompanyDto = xxxCompanyJsonConverter.convertToDto(xxxCompany);
dtoList.add(xxxCompanyDto);
}
return dtoList;
}
}
Maven:
org.springframework
spring-core
4.2.2.BUILD-SNAPSHOT
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-2-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson-2-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-2-version}</version>
</dependency>
//more depedencies
with <jackson-2-version>2.2.2</jackson-2-version> in parent POM
It seems that the JsonView annotations are completely ignored. I can probably use another solution by using two separate DTO classes but it would be nice to get this working as it should!
Any hints are more than welcome!

Validating Mongo Documents in Spring Boot

I'm writing a rest service using spring boot with Jersey and MongoDB starter packages. So I have validation working on top level documents by creating the beans:
#Configuration
public class MongoValidationBeans {
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
I have a document:
#Document
public class SomeDocument {
#NotEmpty(message="error message that shows on console")
private Set<NonDocumentObject> referencesToOtherDocuments;
}
With set of embedded objects:
public class NonDocumentObject {
#NotNull(message="can't see this error message")
private ObjectId referenceId;
#NotBlank
private String referenceInfo;
}
The validation beans respect the #NotEmpty annotation on my set of objects, but they do not respect #NotNull or #NotBlank annotations on fields on my NonDocumentObject. How can I get validation to work on the fields of my embedded Set of objects.
EDIT: #Valid fixes the above problem.
Also, when a constraint violation happens on my top level document, I can see the specific message on my console but tomcat returns an http error page with response status 400. How can I instead send a json object with more information about the error? I have a class
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {}
which catches 404, 405, etc exceptions and returns a json object with the appropriate information, but does not catch mongo constraint validations. I think I need to throw exceptions from the mongo validation beans but can't find resources that direct me how to.
I also want to be able to embed other objects into NonDocumentObject with its own validation. Would it be possible?
So the #Valid annotation triggers cascade validation, but I still can't figure out how to catch validation errors with an exception mapper, or some other way to catch validation errors.

Resources