Skip to content

Validation

REST API or HTML views often allow you to send certain data to the server. The programmer of a given application most often expects that the data will be in a specific form, therefore, before starting processing, he/she must perform validation.

We usually validate the objects by making sure that the "input" object:

  • is built correctly
  • meets certain assumptions (e.g. based on external data - contained in a database).

We can achieve this goal by:

  • writing a component responsible for validation, which in the case of incorrect assumptions, e.g. will throw an exception that will be [handled] (handlluga_wyjatkow.md) by exception handler
  • use of validation annotations

This section focuses on the second method.

Validation annotations

In order to define how a given object should be checked, over its individual fields we can annotate (one or more) from the package javax.validation.constraints (in the future, instead ofjavax, it may be necessary to use jakarta ). Some of the available annotations are:

  • @Null
  • @NotNull
  • @Email
  • @Min
  • @Max
  • @Size
  • @AssertTrue
  • @AssertFalse

These annotations are defined in the JSR-380 standard (the so-called Bean Validation 2.0). One of the most popular libraries that implements this standard and allows the use of these annotations is:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version> <!--a newwer version can be available-->
</dependency>

In order to use all its functionalities, to the project based on Spring Boot, we should add a starter to the dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

WARNING: The hibernate-validator library is in no way related to the hibernate-core library..

hibernate-validator provides access to additional validation annotations that are not defined in Bean Validation 2.0. These are, for example:

  • @Length
  • @URL
  • @Range
  • @NIP, @PESEL, @REGON

The example below defines the object, along with a definition of how it should be validated:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
  private Long id;

  @NotNull
  @Length(min = 3)
  private String name;

  @Min(1)
  private Integer wheelsNumber;
}

Additionally, each of the validation annotations in the message field gives the possibility to enter the message content in the event that a given condition is not met, e.g.

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

  @NotNull
  @Length(min = 3, max = 32, message = "first name has to be between 3 and 32 chars longs")
  private String firstName;

  private String address;

  @JsonIgnore
  @AssertTrue
  public boolean isAddressValid() {
    return address != null && address.split(" ").length >= 2;
  }
}

The above example also uses the @ AssertTrue annotation, which (like@AssertFalse) is used on a method that must:

  • return a boolean
  • have a name starting with is.

If the method result is different than the annotation name suggests, the validation will fail.

NOTE: In a web application that defines a REST API, the objects that are validated are most often also returned as JSON by some endpoints. In the case of using the @AssertTrue/@AssertFalse annotation, the result of the validation method will go to JSON during deserialization, which we often do not want. In this case, we add the annotation @JsonIgnore above the method.

Invoking validation

Having objects that we know how to validate, we need to have a way to invoke this validation. We do this with one of two annotations:

  • @Valid
  • @Validated

The @Valid annotation is most often used on the [web] layer (rest.md), with the object marked with the annotation @RequestBody. In turn, we often use @ Validated on objects that represent [configuration] properties (application_configuration.md # grouping-injected-properties), which forces them to be validated at application startup.

Example with @Valid

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotNull;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {

  @NotNull
  private String from;

  @NotNull
  private String to;

  @NotNull
  @Length(min = 5)
  private String message;
}
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class MessageController {

  @PostMapping("/api/messages")
  public void sendMessage(@Valid @RequestBody final Message message) {
    // handle message sending on service layer
  }
}

When sending the following request:

curl --location --request POST 'http://localhost:8080/api/messages' \
--header 'Content-Type: application/json' \
--data-raw '{
    "from": "",
    "message": "abc"
}'

an exception MethodArgumentNotValidException will be thrown, which we can handle in any way.

Example with @Validated

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Validated
@EnableConfigurationProperties(SomeConfiguration.class)
@Component
@ConfigurationProperties(prefix = "sda.validation.example")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SomeConfiguration {

  @Min(3)
  @NotNull(message = "iterations is a mandatory config field")
  private Integer iterations;
}

If in the application.properties file, add the line:

sda.validation.example.iterations=2

The application should not start and display an error after trying to start:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'sda.validation.example' to pl.sdacademy.sb.validation.SomeConfiguration failed:

    Property: sda.validation.example.iterations
    Value: 2
    Origin: class path resource [application-dev.properties]:2:35
    Reason: must be greater than or equal to 3