Using Bean Validation on Quarkus.io

vepo

Victor Os贸rio

Posted on January 25, 2021

Using Bean Validation on Quarkus.io

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());
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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": []
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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 {};
}
Enter fullscreen mode Exit fullscreen mode

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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}

Run in Postman

You can find all the code on github.com/vepo/quarkus-tutorial.

GitHub logo 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

More information

In the first example, we create a minimal REST API using Quarkus and JAX-RS.

2. Configure JPA Jakarta Persistence

More information

In the second example, we add the persistence layer for our REST API.

3. Configure Jakarta Bean Validation

More information

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.

Man drawing a Software Architecture diagram
Foto de Startup Stock Photos no Pexels

馃挅 馃挭 馃檯 馃毄
vepo
Victor Os贸rio

Posted on January 25, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Using Bean Validation on Quarkus.io
quarkus Using Bean Validation on Quarkus.io

January 25, 2021