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 thehibernate-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