Constraint Validation in Spring Boot Microservices
By: Devlin Verley Ii
Tags: Spring Boot, Microservices, Spring, Java, Programming

Creating the Annotations

We’ll start simple. Let’s say we’ve implemented a Fridge and Pantry Service of which allows us to:

Manage the Fridge and Pantry repositories

Accept POST and/or PUT requests with a JSON payload

We want to validate common fields between request models of both services. Our request model may look something like this:

One of the simplest constraints we can build will involve composing existing constraints, such as @PositiveOrZero and @Max in the example above. This allows us to put an explicit label on common constraints and call it "business logic". Below, we define @FoodQuantity :

There’s a lot going on here, so let’s break this down:

@Constraint marks an annotation as being a Bean Validation constraint and allows us to specify ConstraintValidator implementations; zero, one, or many implementations are welcome here

marks an annotation as being a Bean Validation constraint and allows us to specify implementations; zero, one, or many implementations are welcome here @Retention is set such that our annotation will be retained at runtime

is set such that our annotation will be retained at runtime @Target is set such that we can validate different types of inputs to our services

is set such that we can validate different types of inputs to our services message , groups , and payload are required by @Constraint but do not have to be set — these provide specificity beyond what we'll cover today

By no means is this a simplification. Looking past the verbosity, the annotation opens quite a few doors to make handling complexity a breeze as we’ll see in the next example.

Let’s define a constraint for the category field such that:

Category must be passed and cannot be empty

Only certain categories are allowed to be passed

Categories may differ between the Fridge and Pantry services

To implement this annotation, we will expand upon the premise of our first annotation by adding a custom parameter and providing an implementation of the ConstraintValidator interface. The result looks something like this:

A few more things are happening in this annotation compared to @FoodQuantity . We've specified a new parameter, allowed , to restrict what may be passed into category . Notice the default value — this array is only referenced if values are not passed into @FoodCategory . To handle this constraint, we've implemented FoodCategoryValidator :

Let’s breakdown our new validator class:

ConstraintValidator is parameterized with the annotation class and the type being validated — a String containing the value of category

is parameterized with the annotation class and the type being validated — a containing the value of A global field allowed , set within the overridden initialize method; it is within this method that we gain access to the parameters of @FoodCategory for use throughout the validator class

, set within the overridden method; it is within this method that we gain access to the parameters of for use throughout the validator class isValid is the meat and bones of our constraint validation

is the meat and bones of our constraint validation For invalid scenarios, we disable the default constraint violation, build a proper error message, and return false — this eventually throws an exception we’ll be interested in later

Lastly, to get the most out of our annotation, we’ll propagate the category field into two subclasses pertaining to each service.

After all our hard work, we’ve reached a clean set of request models ready to be bombarded with invalid values: