Partner – Payara – NPI (cat=Jakarta EE)
announcement - icon

Can Jakarta EE be used to develop microservices? The answer is a resounding ‘yes’!

>> Demystifying Microservices for Jakarta EE & Java EE Developers

Course – LS (cat=REST) (INACTIVE)

Get started with Spring and Spring Boot, through the reference Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’re going to take a look at Bean Validation using the open source framework Jersey.

As we’ve already seen in previous articles, Jersey is an open source framework for developing RESTful Web Services. We can get more details about Jersey in our introduction on how to create an API with Jersey and Spring.

2. Bean Validation in Jersey

Validation is the process of verifying that some data obey one or more pre-defined constraints. It is, of course, a very common use case in most applications.

The Java Bean Validation framework (JSR-380) has become the de-facto standard for handling this kind of operation in Java. To recap on the basics of Java Bean Validation please refer to our previous tutorial.

Jersey contains an extension module to support Bean Validation. To use this capability in our application, we first need to configure it. In the next section, we’ll see how to configure our application.

3. Application Setup

Now, let’s build on the simple Fruit API example from the excellent Jersey MVC Support article.

3.1. Maven Dependencies

First of all, let’s add the Bean Validation dependency to our pom.xml:

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-bean-validation</artifactId>
    <version>3.1.1</version>
</dependency>

We can get the latest version from Maven Central.

3.2. Configuring the Server

In Jersey, we normally register the extension feature we want to use in our custom resource configuration class.

However, for the bean validation extension, there is no need to do this registration. Fortunately, this is one of the few extensions that the Jersey framework registers automatically.

Finally, to send validation errors to the client we’ll add a server property to our a custom resource configuration:

public ViewApplicationConfig() {
    packages("com.baeldung.jersey.server");
    property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
}

4. Validating JAX-RS Resource Methods

In this section, we’ll explain two different ways of validating input parameters using constraint annotations:

  • Using built-in Bean Validation API constraints
  • Creating a custom constraint and validator

4.1. Using Built-in Constraint Annotations

Let’s start by looking at built-in constraint annotations:

@POST
@Path("/create")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createFruit(
    @NotNull(message = "Fruit name must not be null") @FormParam("name") String name, 
    @NotNull(message = "Fruit colour must not be null") @FormParam("colour") String colour) {

    Fruit fruit = new Fruit(name, colour);
    SimpleStorageService.storeFruit(fruit);
}

In this example, we create a new Fruit using two form parameters, name and colour. We use the @NotNull annotation which is already part of to the Bean Validation API.

This imposes a simple not null constraint on our form parameters. In case one of the parameters is null, the message declared within the annotation will be returned.

Naturally, we’ll demonstrate this with a unit test:

@Test
public void givenCreateFruit_whenFormContainsNullParam_thenResponseCodeIsBadRequest() {
    Form form = new Form();
    form.param("name", "apple");
    form.param("colour", null);
    Response response = target("fruit/create").request(MediaType.APPLICATION_FORM_URLENCODED)
        .post(Entity.form(form));

    assertEquals("Http Response should be 400 ", 400, response.getStatus());
    assertThat(response.readEntity(String.class), containsString("Fruit colour must not be null"));
}

In the above example, we use the JerseyTest support class to test our fruit resource. We send a POST request with a null colour and check that the response contains the expected message.

For a list of built-in validation constraints, take a look at the docs.

4.2. Defining a Custom Constraint Annotation

Sometimes we need to impose more complex constraints. We can do this by defining our own custom annotation.

Using our simple Fruit API example, let’s imagine we need to validate that all fruit have a valid serial number:

@PUT
@Path("/update")
@Consumes("application/x-www-form-urlencoded")
public void updateFruit(@SerialNumber @FormParam("serial") String serial) {
    //...
}

In this example, the parameter serial must satisfy the constraints defined by @SerialNumber, which we’ll define next.

We’ll first define the constraint annotation:

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { SerialNumber.Validator.class })
    public @interface SerialNumber {

    String message()

    default "Fruit serial number is not valid";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Next, we’ll define the validator class SerialNumber.Validator:

public class Validator implements ConstraintValidator<SerialNumber, String> {
    @Override
    public void initialize(SerialNumber serial) {
    }

    @Override
    public boolean isValid(String serial, 
        ConstraintValidatorContext constraintValidatorContext) {
        
        String serialNumRegex = "^\\d{3}-\\d{3}-\\d{4}$";
        return Pattern.matches(serialNumRegex, serial);
    }
}

The key point here is the Validator class must implement ConstraintValidator where T is the type of value we want to validate, in our case a String.

Finally, we then implement our custom validation logic in the isValid method.

5. Resource Validation

Furthermore, the Bean Validation API also allows us to validate objects using the @Valid annotation.

In the next section, we’ll explain two different ways of validating resource classes using this annotation:

  • First, Request resource validation
  • Second, Response resource validation

Let’s begin by adding the @Min annotation to our Fruit object:

@XmlRootElement
public class Fruit {

    @Min(value = 10, message = "Fruit weight must be 10 or greater")
    private Integer weight;
    //...
}

5.1. Request Resource Validation

First of all, we’ll enable validation using @Valid in our FruitResource class:

@POST
@Path("/create")
@Consumes("application/json")
public void createFruit(@Valid Fruit fruit) {
    SimpleStorageService.storeFruit(fruit);
}

In the above example, if we try to create a fruit with a weight less than 10 we will get a validation error.

5.2. Response Resource Validation

Likewise, in the next example, we’ll see how to validate a response resource:

@GET
@Valid
@Produces("application/json")
@Path("/search/{name}")
public Fruit findFruitByName(@PathParam("name") String name) {
    return SimpleStorageService.findByName(name);
}

Note,  how we use the same @Valid annotation. But this time we use it at the resource method level to be sure the response is valid.

6. Custom Exception Handler

In this last part, we’ll briefly look at how to create a custom exception handler. This is useful when we want to return a custom response if we violate a particular constraint.

Let’s begin by defining our FruitExceptionMapper:

public class FruitExceptionMapper implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException exception) {
        return Response.status(Response.Status.BAD_REQUEST)
            .entity(prepareMessage(exception))
            .type("text/plain")
            .build();
    }

    private String prepareMessage(ConstraintViolationException exception) {
        StringBuilder message = new StringBuilder();
        for (ConstraintViolation<?> cv : exception.getConstraintViolations()) {
            message.append(cv.getPropertyPath() + " " + cv.getMessage() + "\n");
        }
        return message.toString();
    }
}

First of all, we define a custom exception mapping provider. In order to do this, we implement the ExceptionMapper interface using a ConstraintViolationException.

Hence, we’ll see that when this exception is thrown the toResponse method of our custom exception mapper instance will be invoked.

Also, in this simple example, we iterate through all the violations and append each property and message to be sent back in the response.

Next, in order to use our custom exception mapper we need to register our provider:

@Override
protected Application configure() {
    ViewApplicationConfig config = new ViewApplicationConfig();
    config.register(FruitExceptionMapper.class);
    return config;
}

Finally, we add an endpoint to return an invalid Fruit to show the exception handler in action:

@GET
@Produces(MediaType.TEXT_HTML)
@Path("/exception")
@Valid
public Fruit exception() {
    Fruit fruit = new Fruit();
    fruit.setName("a");
    fruit.setColour("b");
    return fruit;
}

7. Conclusion

To summarize, in this tutorial, we’ve explored the Jersey Bean Validation API extension.

First, we started by introducing how the Bean Validation API can be used in Jersey. Also, we took a look at how to configure an example web application.

Finally, we looked at several ways of doing validation with Jersey and how to write a custom exception handler.

As always, the full source code of the article is available over on GitHub.

Course – LS (cat=REST)

Get started with Spring and Spring Boot, through the Learn Spring course :

>> CHECK OUT THE COURSE
res – REST (eBook) (cat=REST)
Comments are closed on this article!