Validation (Spring Boot)
Introduction
Let's see a Spring Boot 2.2.6.RELEASE application using both Bean Validation (JSR 380) and custom validations.
References
JavaBean Validation - Predefined constraints (including JSR 380)
https://www.logicbig.com/tutorials/java-ee-tutorial/bean-validation/predefined_constraints.html
Custom validator @CheckCase JBoss example
Spring REST Validation Example
https://mkyong.com/spring-boot/spring-rest-validation-example/
Guide to Internationalization in Spring Boot
https://www.baeldung.com/spring-boot-internationalization
POM configuration (pom.xml)
<dependency>
<!-- Only explicitly needed if not using spring-boot-starter-web -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
import: jakarta vs javax
For annotations, use the proper import:
jakarta.validation.constraints.Email javax.validation.constraints.Email
jakarta.validation.constraints.NotBlank javax.validation.constraints.NotBlank
jakarta.validation.constraints.NotNull javax.validation.constraints.NotNull
jakarta.validation.constraints.Pattern javax.validation.constraints.Pattern
jakarta.validation.constraints.Positive javax.validation.constraints.Positive
jakarta.validation.constraints.Size javax.validation.constraints.Size
REST operation parameter path or query string
Decorate the path or query string parameter with validation contraint, eg: @NotBlank
@Parameter(description = "Language", required = true, example = "ca") @RequestParam(
name = "lang", required = true) @NotBlank(message = "{error.lang.notblank}") String lang,
REST operation parameter request body
Decorate the RestController with @Validated
@Validated
@RestController
public class MyController {
Decorate the body parameter with @Valid
@RequestBody(required = true) @Valid SampleValDto in
where SampleValDto is:
@Data
public class SampleValDto {
/** Must be a valid ISO 639-1 code */
@Schema(description = "Lang ISO-639-1", required = false)
@NotBlank(message = "{error.lang.notblank}")
@Nullable
private String langIso639p1Code;
}
Internationalization of constrain messages
Spring Boot replaces the message key of the constraint by the corresponding message in the user language.
Find in this site how to configure localized messages at page "MessageSource (Spring Boot)":
https://sites.google.com/site/pawneecity/sprint-boot/messagesource-spring-boot
For this sample, the following file resources have been used:
messages.properties
# Errors
error.lang.notblank=Language code must not be null and must contain at least one non-whitespace character
# Validation constraints (the default resource key is the fully qualified name of the annotation class concat. to .message)
edu.cou.app.validator.LangIso639p1App.message=Unknown language ISO 639-1 code
javax.validation.constraints.NotBlank.message=Value must not be null and must contain at least one non-whitespace character
messages_pl.properties
# Errors
error.lang.notblank=Kod języka nie może mieć wartości null i musi zawierać co najmniej jeden znak spacji
# Validation constraints (the default resource key is the fully qualified name of the annotation class concat. to .message)
edu.cou.app.validator.LangIso639p1App.message=Nieznany kod ISO 639-1
javax.validation.constraints.NotBlank.message=Nie może mieć wartości null i musi zawierać co najmniej jeden znak spacji
Override handling of exception MethodArgumentNotValidException
When the constraint fails, it throws this exception.
In order to provide the invoker with the proper message, ensure you custom exception handler overrides the default response behaviour (status 400 without body).
Find in this site how to override the method handleMethodArgumentNotValid at page "Error handling (Spring Boot)":
https://sites.google.com/site/pawneecity/sprint-boot/error-handling-rest-spring-boot
Custom validator
Let's create a custom annotation constraint for validating an ISO 639-1 code.
This custom annotation, @LangIso639p1App, can be used at a parameter or at an attribute.
For demonstrating purposes, code assumes as valid only three codes: en, es, pl.
The annotation
package edu.cou.app.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RUNTIME)
@Constraint(validatedBy = LangIso639p1AppValidator.class)
@Documented
public @interface LangIso639p1App {
/** Default resource key is the fully qualified name of the annotation class concatenated to .message */
String message() default "{edu.cou.app.validator.LangIso639p1App.message}";
/***/
Class<?>[] groups() default {};
/***/
Class<? extends Payload>[] payload() default {};
}
The validator
package edu.cou.app.validator;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
*
*/
public class LangIso639p1AppValidator implements ConstraintValidator<LangIso639p1App, String> {
private final List<String> isoCodes = Arrays.asList("en", "es", "pl");
@Override
public boolean isValid(@Nullable String value, ConstraintValidatorContext context) {
if (value == null){
return true;
}
return isoCodes.contains(value);
}
}
Regular expressions
AWS S3 object key name safe chars
String REGEX_OBJECT_KEY_SAFE_CHARS = "^[0-9a-zA-Z\\!\\-\\_\\.\\*'()]+$";
String REGEX_OBJECT_KEY_SAFE_CHARS_PLUS_SLASH = "^[0-9a-zA-Z\\!\\-\\_\\.\\*'()/]+$";
Email
HTML 5
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
Java
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
NIF / NIE
var nifRegex = /^[0-9]{8}[TRWAGMYFPDXBNJZSQVHLCKE]$/i;
var nieRegex = /^[XYZ][0-9]{7}[TRWAGMYFPDXBNJZSQVHLCKE]$/i;
String, even it has line breaks, does not contain 'badword'
String REGEX_STR_WITHOUT_BADWORD = "(?s)^((?!badword).)*$";
Examples
@Max(27L) for CharSequence, Collection, Map, Array
@Size(min =27, max = 30)
@Pattern(regexp=String, flags=Pattern.Flag[]) for CharSequence (String)
Exactly 5 digits, eg: "20202"
@Pattern(regexp = "^\\d{5}$")
One of words specified
@Pattern(regexp = "apple|banana|melon")
String (even multiline) doesn't contain 2 consecutive slashes
@Pattern(regexp = "(?s)^((?!//).)*$")