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)
public User create(CreateUserRequest request) {
Objects.requireNonNull(request.getUsername(), "\"username\" cannot be null!");
if (request.getUsername().isBlank()) {
throw new BadRequestException("\"username\" may not be blank");
} else if (!request.getUsername().matches("^[a-zA-Z][a-zA-Z0-9] $")) {
throw new BadRequestException("\"username\" should start with a letter and should only accept letters and numbers");
} else if (request.getUsername().length() < 4 || request.getUsername().length() > 15) {
throw new BadRequestException("\"username\" should have size [4,15]");
}
return users.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.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
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
.
@POST
@Produces(MediaType.APPLICATION_JSON)
public User create(@Valid CreateUserRequest request) {
return users.create(User.builder()
.email(request.getEmail())
.username(request.getUsername())
.firstName(request.getFirstName())
.lastName(request.getLastName()).admin(request.isAdmin())
.hashedPassword(request.getHashedPassword())
.build());
}
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
.
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class CreateUserRequest {
@Email
@NotBlank(message = "email may not be blank")
private String email;
@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")
private String username;
@NotBlank(message = "firstName may not be blank")
private String firstName;
@NotBlank(message = "lastName may not be blank")
private String lastName;
private boolean admin;
@NotBlank(message = "hashedPassword may not be blank")
private String hashedPassword;
// [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
.
public Optional<User> findByUsername(@Size(min = 4, max = 15) String username) {
TypedQuery<User> query = em.createNamedQuery("User.findByUsername", User.class);
query.setParameter("username", username);
return query.getResultStream().findFirst();
}
Creating Custom Validations
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)
@ReportAsSingleViolation
public @interface ReservedWord {
String value();
String message() default "You are using a Reserved Word";
Class<? extends Payload>[] 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.
public class ReservedWordValidator implements ConstraintValidator<ReservedWord, String> {
private String word;
@Override
public void initialize(ReservedWord wordAnnotation) {
this.word = wordAnnotation.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value.compareToIgnoreCase(word) != 0;
}
}
Now you can use it in your service.
Executing and Testing
With the database running you only need to start the Quarkus using maven.
mvn quarkus:dev
In our example, we have provided 2 endpoints where you can test with valid and invalid parameters.
- Create an User:
POST /user
- Find User by Username:
GET /user/{username}
You can find all the code on github.com/vepo/quarkus-tutorial.
vepo / quarkus-tutorial
This is a series of blog posts where I will create a tutorial of Quakus.io.
Quarkus Tutorial
Steps
1. Create a REST API
In the first example, we create a minimal REST API using Quarkus and JAX-RS.
2. Configure JPA Jakarta Persistence
In the second example, we add the persistence layer for our REST API.
3. Configure Jakarta Bean Validation
In the third example, we add the validation to all layers of our REST API.
Design by Contract
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.
Top comments (0)