Jakarta Bean Validation is a very useful specification. I can't find any reason to do not use it. If you know, please share with me.
The worst way to do it
Validation is a boring feature. It is important, but most of the time it pollutes the code. You have to check function by function if all values are according to the expected.
So, let's imagine the in the code of our Step 02 we need to validate that the username is a String not empty, with a minimum size of 4 and a maximum of 15 and no whitespace. How can we do it?
@POST@Produces(MediaType.APPLICATION_JSON)publicUsercreate(CreateUserRequestrequest){Objects.requireNonNull(request.getUsername(),"\"username\" cannot be null!");if(request.getUsername().isBlank()){thrownewBadRequestException("\"username\" may not be blank");}elseif(!request.getUsername().matches("^[a-zA-Z][a-zA-Z0-9]+$")){thrownewBadRequestException("\"username\" should start with a letter and should only accept letters and numbers");}elseif(request.getUsername().length()<4||request.getUsername().length()>15){thrownewBadRequestException("\"username\" should have size [4,15]");}returnusers.create(User.builder().email(request.getEmail()).username(request.getUsername()).firstName(request.getFirstName()).lastName(request.getLastName()).admin(request.isAdmin()).hashedPassword(request.getHashedPassword()).build());}
Jakarta Bean Validation allow us to remove all this code and replace with simple annotations.
Configuring JPA in an existent Quarkus Project
To enable Jakarta Bean Validation, you should add it's implementation to Quarkus, that is Hibernate Validator.
That is all you need! Now you just need to configure where you will use and what fields you want to validate.
Requiring Valid Parameters
The next step, you should informe Quarkus, where you want to use the validate. From the example above, we can remove all validation lines and just add the annotation javax.validation.Valid.
Then we need inform Quarkus the logic for this validation, it can be done inside CreataUserRequest class. We will use the annotations Email, NotBlank, Pattern and Size.
importjavax.validation.constraints.Email;importjavax.validation.constraints.NotBlank;importjavax.validation.constraints.Pattern;importjavax.validation.constraints.Size;publicclassCreateUserRequest{@Email@NotBlank(message="email may not be blank")privateStringemail;@Size(min=4,max=15,message="username should have size [{min},{max}]")@NotBlank(message="username may not be blank")@Pattern(regexp="^[a-zA-Z][a-zA-Z0-9]+$",message="\"username\" should start with a letter and should only accept letters and numbers")privateStringusername;@NotBlank(message="firstName may not be blank")privateStringfirstName;@NotBlank(message="lastName may not be blank")privateStringlastName;privatebooleanadmin;@NotBlank(message="hashedPassword may not be blank")privateStringhashedPassword;// [Getters and Setters]}
This can be used in any Managed Bean inside Quarkus, but if you used on Endpoints it will enable HTTP validation returning a Bad Request response, as we can see in the response bellow. This is not a good way to present errors on a REST API, but at least follow some patterns as returning the correct HTTP Status Code and informing all constraint violations.
{"classViolations":[],"parameterViolations":[{"constraintType":"PARAMETER","message":"\"username\" should start with a letter and should only accept letters and numbers","path":"create.request.username","value":"2vepo"}],"propertyViolations":[],"returnValueViolations":[]}
If you need to add validation for a parameter that you do not implement the class, like a String or a primitive type, you can use the annotations directly on the bean paramenter. In this case you can ommit the Valid.
Now that we are able to use the Built-in validations, let's create some custom validators. First we need to define the annotation for it. It should have the following pattern.
@Documented@Constraint(validatedBy=ReservedWordValidator.class)@Target({METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER,TYPE_USE})@Retention(RUNTIME)@Repeatable(ReservedWords.class)@SupportedValidationTarget(ANNOTATED_ELEMENT)@ReportAsSingleViolationpublic@interfaceReservedWord{Stringvalue();Stringmessage()default"You are using a Reserved Word";Class<?extendsPayload>[]payload()default{};Class<?>[]groups()default{};}
In our case we are creating a Repeatable just for an example, but you can set any kind of Type for value. Then we need to declare and implement the Validator. As you can see, we are already linking the Validator with the Annotation using @Constraint(validatedBy = ReservedWordValidator.class), now we only need to implement it.
The most important concept on Validating parameters is Design By Contract. A contract defines you rights and responsability, if you define a contract you will not handle values outside that contract. And using Bean Validation enable you to implement Orthogonal Contracts, keeping your code clear. You do not mix validation with business logic. And you don't need to replicated code, only adding an annotation you can spread validation in all your Managed Beans.
Conclusion
Quarkus is easy to configure. You can remove a lot of code, only creating validations.