{{announcement.body}}
{{announcement.title}}

Validate Your Microservices With MicroProfile and Bean Validation

DZone 's Guide to

Validate Your Microservices With MicroProfile and Bean Validation

Why and how

· Microservices Zone ·
Free Resource

Can someone validate these for me?

Can someone validate these for me?

Is a common code practice in our microservices to have a lot of If’s blocks to validate if some values are null, or if they have the right size, the dates are correct, etc. then the developer needs to inform the user that something went wrong and 50% of our code are those validations.

Fortunately, within the JavaEE/JakartaEE API’s there is Bean Validation that allows the developer to reduce all of that validation code in a declarative way using annotations and can be used along with MicroProfile to write better microservices that not only informs the users that something went wrong but also let them know how to fix it.

All the code used in this post can be found at this repository:

https://github.com/Motojo/MicroProfile-BeanValidation

You may also like: The Importance of Validating the Testing Infrastructure

Integrating Bean Validation in our project

dependencies {
   /*--- Full JavaEE dependency or just javax.validation dependency   ---*/
   /* providedCompile group: 'javax', name: 'javaee-api', version: '8.0' */
   providedCompile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
   providedCompile group: 'org.eclipse.microprofile', name: 'microprofile', version: '2.1'
}
Example using Gradle

Then the developer can start using the Bean Validation annotations on any POJO, for example:

  • @NotNull: Verifies that the value is not null.
  • @AssertTrue: Verifies that the value is true.
  • @Size: Verifies that the size of the value is between the min and max specified, can be used with Strings, Maps, Collections, and Arrays.
  • @Min: Verifies that the numeric value is equal or greater than the specified value.
  • @Max: Verifies that the numeric value is equal or lower than the specified value.
  • @Email: Verifies that the value is a valid email.
  • @NotEmpty: Verifies that the value is not empu, applies to Strings, Collections, Maps, and Arrays.
  • @NotBlank: Verifies that the text is not whitespaces.
  • @Positive / @PositiveOrZero: Verifies that the numeric value is positive including or not the zero.
  • @Negative / @NegativeOrZero: Verifies that the numeric value is negative including or not the zero.
  • @Past / @PastOrPresent: Verifies that the date is in the past including or not the present.
  • @Future / @FutureOrPresent: Verifies that the date is in the future including or not the present.

Can be used like this:

public class Book
{
   private Long id;

   @NotNull
   @Size(min = 6)
   private String name;

   @NotNull
   private String author;

   @Min(6)
   @Max(200)
   @NotNull
   private Integer pages;
}
Example of annotated POJO with validation

Integrating Bean Validation With JAX-RS

When Bean Validation is integrated into the project and POJOS are annotated is time to tell to JAX-RS endpoints to make or not the validations using the annotation @Valid like this:

@POST
public Book createBook(@Valid Book book)
{
   //Do something like saving to DB and then return the book with a new ID
   book.setId(20L);
   return book;
}

Example of JAX-RS endpoint annotated to validate the Book Object

When calling the service all the validation on the Book objects will be executed and an error will be returned if something went wrong or if everything is ok the service code will be executed.

Web Service executed successfully.

Web Service with error (name not sent)

Better Error Management

As shown in the previous image, when there is an error the service code was not executed and the standard error response of the server is sent, but this is not very useful at all and can be a cause of more errors if the client expects that all the responses will be returned in JSON format.

To improve the error responses and let the user know what happened an interceptor of type  ExceptionMapper can be written to transform the standard response into JSON and add more useful information to it.


@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException>
{
   private String getPropertyName(Path path)
   {
       //The path has the form of com.package.class.property
       //Split the path by the dot (.) and take the last segment.
       String[] split = path.toString().split("\\.");
       return split[split.length - 1];
   }

   @Override
   public Response toResponse(ConstraintViolationException exception)
   {
       Map<String, String> errors = exception.getConstraintViolations().stream()
       .collect(Collectors.toMap(v -> getPropertyName(v.getPropertyPath()), ConstraintViolation::getMessage));

       return Response.status(Response.Status.BAD_REQUEST)
               .entity(errors).type(MediaType.APPLICATION_JSON)
               .build();
   }
}
Exception mapper to transform the error response


Once the exception mapper is registered using the @Provider annotation and calling the service if there is an error response like this will be returned:

WebService with a JSON error report with details

Query, Path, and Header Params

Bean Validation is not limited to be used in request objects, also can be used on Query, Path and Header params like this:


@GET
public Book getBook(@Valid @NotNull @QueryParam("id") Long id)
{
   //Just build a dummy book to return
   Book book = new Book();
   book.setId(id);
   book.setAuthor("Jorge Cajas");
   book.setPages(100);

   return book;
}


But calling this service, at the error response we can be noted that the ExceptionMapper previously wrote is not useful at all because the parameter name was not resolved.

The id parameter name was not resolved

To fix this Bean Validation must be configured in a way it knows how to resolve the JAX-RS method’s parameter names.

The validation.xml is used to configure various aspects of Bean Validation and must be placed at resources/META-INF directory. On this file, we will use the <parameter-name-provider> property with a reference to a class that will be responsible to resolve the JAX-RS parameter names.


<validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.1.xsd" version="1.1">

   <parameter-name-provider>com.demo.validations.CustomParameterNameProvider</parameter-name-provider>

</validation-config>


resources/META-INF/validation.xml

The class CustomParameterNameProvider will inspect the JAX-RS methods and from the annotations of Query, Path or Header params will complete the necessary information in order to resolve correctly the parameter names on the Exception Mapper previously written.


public class CustomParameterNameProvider implements ParameterNameProvider
{
   @Override
   public List<String> getParameterNames(Constructor<?> constructor){
       return lookupParameterNames(constructor.getParameterAnnotations());
   }

   @Override
   public List<String> getParameterNames(Method method) {
       return lookupParameterNames(method.getParameterAnnotations());
   }

   private List<String> lookupParameterNames(Annotation[][] annotations) {
       final List<String> names = new ArrayList<>();
       if (annotations != null) {
           for (Annotation[] annotation : annotations) {
               String annotationValue = null;
               for (Annotation ann : annotation) {
                   annotationValue = getAnnotationValue(ann);
                   if (annotationValue != null) {
                       break;
                   }
               }
               // if no matching annotation, must be the request body
               if (annotationValue == null) {
                   annotationValue = "requestBody";
               }
               names.add(annotationValue);
           }
       }
       return names;
   }

   private static String getAnnotationValue(Annotation annotation) {
       if (annotation instanceof HeaderParam) {  return ((HeaderParam) annotation).value(); }
       else if (annotation instanceof PathParam) { return ((PathParam) annotation).value(); }
       else if (annotation instanceof QueryParam) { return ((QueryParam) annotation).value(); }
       return null;
   }
}


CustomParameterNameProvider.java


Once Bean Validation is configured, a call to the service with error will be like this:

Id query param name resolver correctly


Further Reading

What is Data Validation?

Validation in Java Applications

Topics:
microservices ,validate ,microprofile ,bean validation

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}