1. <dependency>
    2. <groupId>io.micronaut</groupId>
    3. <artifactId>micronaut-validation</artifactId>
    4. </dependency>

    Note that Micronaut’s implementation is not currently fully compliant with the as the specification heavily relies on reflection-based APIs.

    The following features are unsupported at this time:

    • Annotations on generic argument types since only the Java language supports this feature.

    • Any interaction with the constraint metadata API, since Micronaut uses already computed compilation time metadata.

    • XML-based configuration

    • Instead of using javax.validation.ConstraintValidator you should use (io.micronaut.validation.validator.constraints.ConstraintValidator) to define custom constraints, which supports validating annotations at compilation time.

    Micronaut’s implementation includes the following benefits:

    • Reflection and Runtime Proxy free validation resulting in reduced memory consumption

    • Smaller JAR size since Hibernate Validator adds another 1.4MB

    • Faster startup since Hibernate Validator adds 200ms+ startup overhead

    • Configurability via Annotation Metadata

    • Support for Reactive Bean Validation

    • Automatic compatibility with GraalVM native without additional configuration

    If you require full Bean Validator 2.0 compliance you can add the micronaut-hibernate-validator module to your classpath, which will replace Micronaut’s implementation.

    1. implementation("io.micronaut.beanvalidation:micronaut-hibernate-validator")
    1. <dependency>
    2. <groupId>io.micronaut.beanvalidation</groupId>
    3. <artifactId>micronaut-hibernate-validator</artifactId>
    4. </dependency>

    You can validate methods of any class declared as a Micronaut bean simply by using the javax.validation annotation as arguments:

    1. import javax.inject.Singleton;
    2. import javax.validation.constraints.NotBlank;
    3. @Singleton
    4. public class PersonService {
    5. public void sayHello(@NotBlank String name) {
    6. System.out.println("Hello " + name);
    7. }
    8. }
    1. import javax.inject.Singleton
    2. import javax.validation.constraints.NotBlank
    3. @Singleton
    4. class PersonService {
    5. void sayHello(@NotBlank String name) {
    6. println "Hello $name"
    7. }
    8. }
    1. import javax.inject.Singleton
    2. import javax.validation.constraints.NotBlank
    3. @Singleton
    4. open class PersonService {
    5. open fun sayHello(@NotBlank name: String) {
    6. println("Hello $name")
    7. }
    8. }

    The above example declares that the @NotBlank annotation should be validated when executing the sayHello method.

    If a validation error occurs a javax.validation.ConstraintViolationException will be thrown. For example:

    1. import io.micronaut.test.annotation.MicronautTest;
    2. import org.junit.jupiter.api.Test;
    3. import static org.junit.jupiter.api.Assertions.*;
    4. import javax.inject.Inject;
    5. import javax.validation.ConstraintViolationException;
    6. @MicronautTest
    7. class PersonServiceSpec {
    8. @Inject PersonService personService;
    9. @Test
    10. void testThatNameIsValidated() {
    11. final ConstraintViolationException exception =
    12. assertThrows(ConstraintViolationException.class, () ->
    13. personService.sayHello("") (1)
    14. );
    15. assertEquals("sayHello.name: must not be blank", exception.getMessage()); (2)
    16. }
    17. }
    1. import io.micronaut.test.annotation.MicronautTest
    2. import spock.lang.Specification
    3. import javax.inject.Inject
    4. import javax.validation.ConstraintViolationException
    5. @MicronautTest
    6. class PersonServiceSpec extends Specification {
    7. @Inject PersonService personService
    8. void "test person name is validated"() {
    9. when:"The sayHello method is called with a blank string"
    10. personService.sayHello("") (1)
    11. then:"A validation error occurs"
    12. def e = thrown(ConstraintViolationException)
    13. e.message == "sayHello.name: must not be blank" // (2)
    14. }
    15. }
    1. import io.micronaut.test.annotation.MicronautTest
    2. import org.junit.jupiter.api.Test
    3. import org.junit.jupiter.api.Assertions.*
    4. import javax.inject.Inject
    5. import javax.validation.ConstraintViolationException
    6. @MicronautTest
    7. class PersonServiceSpec {
    8. @Inject
    9. lateinit var personService: PersonService
    10. @Test
    11. fun testThatNameIsValidated() {
    12. val exception = assertThrows(ConstraintViolationException::class.java
    13. ) { personService.sayHello("") } (1)
    14. assertEquals("sayHello.name: must not be blank", exception.message) (2)
    15. }
    16. }
    1The method is called with a blank string
    2An exception occurs

    If you wish to validate data classes, such as POJOs and so on, typically used in JSON interchange, the class must be annotated with @Introspected (see the previous section on ) or, if the class is external, be imported by the @Introspected annotation.

    1. import io.micronaut.core.annotation.Introspected;
    2. import javax.validation.constraints.*;
    3. @Introspected
    4. public class Person {
    5. private String name;
    6. @Min(18)
    7. private int age;
    8. @NotBlank
    9. public String getName() {
    10. return name;
    11. }
    12. public int getAge() {
    13. return age;
    14. }
    15. public void setName(String name) {
    16. this.name = name;
    17. }
    18. public void setAge(int age) {
    19. this.age = age;
    20. }
    21. }
    1. import io.micronaut.core.annotation.Introspected
    2. import javax.validation.constraints.Min
    3. @Introspected
    4. class Person {
    5. @NotBlank
    6. String name
    7. @Min(18l)
    8. int age
    9. }
    1. import io.micronaut.core.annotation.Introspected
    2. import javax.validation.constraints.Min
    3. import javax.validation.constraints.NotBlank
    4. @Introspected
    5. data class Person(
    6. @field:NotBlank var name: String,
    7. @field:Min(18) var age: Int
    8. )
    The @Introspected annotation can be used as a meta-annotation and common annotations like @javax.persistence.Entity are treated as @Introspected

    The above example defines a class called Person that has 2 properties (name and age) that have constraints applied to them. Note that in Java the annotations can be on the field or the getter and with Kotlin data classes the annotation should target the field.

    If you wish to validate the class manually then you can inject an instance of :

    1. @Inject
    2. Validator validator;
    3. @Test
    4. void testThatPersonIsValidWithValidator() {
    5. Person person = new Person();
    6. person.setName("");
    7. person.setAge(10);
    8. final Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person); (1)
    9. assertEquals(2, constraintViolations.size()); (2)
    10. }
    1. @Inject
    2. lateinit var validator: Validator
    3. @Test
    4. fun testThatPersonIsValidWithValidator() {
    5. val person = Person("", 10)
    6. val constraintViolations = validator.validate(person) (1)
    7. assertEquals(2, constraintViolations.size) (2)
    8. }

    Alternatively on Bean methods you can use javax.validation.Valid to trigger cascading validation:

    1. @Singleton
    2. public class PersonService {
    3. public void sayHello(@Valid Person person) {
    4. System.out.println("Hello " + person.getName());
    5. }
    6. }
    1. @Singleton
    2. class PersonService {
    3. void sayHello(@Valid Person person) {
    4. println "Hello $person.name"
    5. }
    6. }
    1. @Singleton
    2. open class PersonService {
    3. open fun sayHello(@Valid person: Person) {
    4. println("Hello ${person.name}")
    5. }
    6. }

    The PersonService will now validate the Person class when invoked:

    1. @Inject
    2. PersonService personService;
    3. @Test
    4. void testThatPersonIsValid() {
    5. Person person = new Person();
    6. person.setName("");
    7. person.setAge(10);
    8. final ConstraintViolationException exception =
    9. assertThrows(ConstraintViolationException.class, () ->
    10. personService.sayHello(person) (1)
    11. );
    12. assertEquals(2, exception.getConstraintViolations().size()); (2)
    13. }
    1. @Inject PersonService personService
    2. void "test person name is validated"() {
    3. when:"The sayHello method is called with an invalid person"
    4. personService.sayHello(new Person(name: "", age: 10)) (1)
    5. then:"A validation error occurs"
    6. def e = thrown(ConstraintViolationException)
    7. e.constraintViolations.size() == 2 // (2)
    8. }
    1. @Inject
    2. lateinit var personService: PersonService
    3. @Test
    4. fun testThatPersonIsValid() {
    5. val person = Person("", 10)
    6. val exception = assertThrows(ConstraintViolationException::class.java (1)
    7. ) { personService.sayHello(person) }
    8. assertEquals(2, exception.constraintViolations.size) (2)
    9. }
    1A validated method is invoked
    2The constraint violations are verified

    You can also validate the properties of classes that are annotated with @ConfigurationProperties to ensure configuration is correct.

    It is recommended that you annotate that features validation with @Context to ensure that the validation occurs at startup time.

    To define additional constraints you can define a new annotation, for example:

    1. import javax.validation.Constraint;
    2. import java.lang.annotation.*;
    3. import static java.lang.annotation.ElementType.*;
    4. import static java.lang.annotation.RetentionPolicy.RUNTIME;
    5. @Retention(RetentionPolicy.RUNTIME)
    6. @Constraint(validatedBy = { }) (1)
    7. public @interface DurationPattern {
    8. String message() default "invalid duration ({validatedValue})"; (2)
    9. /**
    10. * Defines several constraints on the same element.
    11. */
    12. @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    13. @Retention(RUNTIME)
    14. @Documented
    15. @interface List {
    16. DurationPattern[] value(); (3)
    17. }
    18. }
    1. import javax.validation.Constraint
    2. import java.lang.annotation.*
    3. import static java.lang.annotation.RetentionPolicy.RUNTIME
    4. @Retention(RUNTIME)
    5. @Constraint(validatedBy = []) (1)
    6. @interface DurationPattern {
    7. String message() default "invalid duration ({validatedValue})" (2)
    8. }
    1. import javax.validation.Constraint
    2. @Retention(AnnotationRetention.RUNTIME)
    3. @Constraint(validatedBy = []) (1)
    4. annotation class DurationPattern(
    5. val message: String = "invalid duration ({validatedValue})" (2)
    6. )
    You can add messages and message bundles using the and ResourceBundleMessageSource classes.

    The latter approach is recommended if you plan to define multiple validators:

    1. import io.micronaut.context.annotation.Factory;
    2. import io.micronaut.validation.validator.constraints.ConstraintValidator;
    3. import javax.inject.Singleton;
    4. @Factory
    5. public class MyValidatorFactory {
    6. @Singleton
    7. ConstraintValidator<DurationPattern, CharSequence> durationPatternValidator() {
    8. return (value, annotationMetadata, context) ->
    9. value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$");
    10. }
    1. import io.micronaut.context.annotation.Factory
    2. import io.micronaut.core.annotation.AnnotationValue
    3. import io.micronaut.validation.validator.constraints.*
    4. import javax.inject.Singleton
    5. class MyValidatorFactory {
    6. @Singleton
    7. ConstraintValidator<DurationPattern, CharSequence> durationPatternValidator() {
    8. return { CharSequence value,
    9. AnnotationValue<DurationPattern> annotation,
    10. ConstraintValidatorContext context ->
    11. return value == null || value.toString() ==~ /^PT?[\d]+[SMHD]{1}$/
    12. } as ConstraintValidator<DurationPattern, CharSequence>
    13. }
    14. }
    1. import io.micronaut.context.annotation.Factory
    2. import io.micronaut.validation.validator.constraints.ConstraintValidator
    3. import javax.inject.Singleton
    4. @Factory
    5. class MyValidatorFactory {
    6. @Singleton
    7. fun durationPatternValidator() : ConstraintValidator<DurationPattern, CharSequence> {
    8. return ConstraintValidator { value, annotation, context ->
    9. value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$".toRegex())
    10. }
    11. }
    12. }

    The above example implements a validator that validates any field, parameter etc. that is annotated with DurationPattern, ensuring that the string can be parsed with java.time.Duration.parse.

    Generally null is regarded as valid and @NotNull used to constrain a value as not being null. The example above regards null as a valid value.

    For example:

    1. @Singleton
    2. class HolidayService {
    3. String startHoliday(@NotBlank String person,
    4. @DurationPattern String duration) {
    5. final Duration d = Duration.parse(duration)
    6. return "Person $person is off on holiday for ${d.toMinutes()} minutes"
    7. }
    8. }
    1. @Singleton
    2. open class HolidayService {
    3. open fun startHoliday( @NotBlank person: String,
    4. @DurationPattern duration: String): String {
    5. val d = Duration.parse(duration)
    6. val mins = d.toMinutes()
    7. return "Person $person is off on holiday for $mins minutes"
    8. }
    9. }

    To verify the above examples validates the duration parameter you can define a test:

    1. @Inject HolidayService holidayService;
    2. @Test
    3. void testCustomValidator() {
    4. final ConstraintViolationException exception =
    5. assertThrows(ConstraintViolationException.class, () ->
    6. holidayService.startHoliday("Fred", "junk") (1)
    7. );
    8. assertEquals("startHoliday.duration: invalid duration (junk)", exception.getMessage()); (2)
    9. }
    1. void "test test custom validator"() {
    2. when:"A custom validator is used"
    3. holidayService.startHoliday("Fred", "junk") (1)
    4. then:"A validation error occurs"
    5. def e = thrown(ConstraintViolationException)
    6. e.message == "startHoliday.duration: invalid duration (junk)" // (2)
    7. }
    1. @Inject
    2. lateinit var holidayService: HolidayService
    3. @Test
    4. fun testCustomValidator() {
    5. val exception = assertThrows(ConstraintViolationException::class.java
    6. ) { holidayService.startHoliday("Fred", "junk") } (1)
    7. assertEquals("startHoliday.duration: invalid duration (junk)", exception.message) (2)
    8. }

    You can use Micronaut’s validator to validate annotation usages at compilation time. To do so you should include micronaut-validation in the annotation processor classpath:

    1. annotationProcessor("io.micronaut:micronaut-validation")
    1. <annotationProcessorPaths>
    2. <path>
    3. <groupId>io.micronaut</groupId>
    4. <artifactId>micronaut-validation</artifactId>
    5. </path>
    6. </annotationProcessorPaths>

    Once this is done Micronaut will at compilation validate annotation values that are themselves annotated with javax.validation. For example consider the following annotation:

    1. import java.lang.annotation.*;
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface TimeOff {
    4. @DurationPattern
    5. String duration();
    6. }
    1. import java.lang.annotation.*
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @interface TimeOff {
    4. @DurationPattern
    5. String duration()
    6. }
    1. @Retention(AnnotationRetention.RUNTIME)
    2. annotation class TimeOff(
    3. @DurationPattern val duration: String
    4. )

    If your attempt to use @TimeOff(duration="junk") in your source Micronaut will fail compilation due to the value of duration violating the DurationPattern constraint.

    If duration is a property placeholder such as @TimeOff(duration=”${my.value}”) then validation handling will be deferred until runtime.

    Note that if you wish to allow use of a custom ConstraintValidator at compilation time you should instead define the validator as a class:

    1. import edu.umd.cs.findbugs.annotations.NonNull;
    2. import edu.umd.cs.findbugs.annotations.Nullable;
    3. import io.micronaut.core.annotation.AnnotationValue;
    4. import io.micronaut.validation.validator.constraints.*;
    5. public class DurationPatternValidator implements ConstraintValidator<DurationPattern, CharSequence> {
    6. @Override
    7. public boolean isValid(
    8. @Nullable CharSequence value,
    9. @NonNull AnnotationValue<DurationPattern> annotationMetadata,
    10. @NonNull ConstraintValidatorContext context) {
    11. return value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$");
    12. }
    13. }
    1. import edu.umd.cs.findbugs.annotations.NonNull
    2. import edu.umd.cs.findbugs.annotations.Nullable
    3. import io.micronaut.core.annotation.AnnotationValue
    4. import io.micronaut.validation.validator.constraints.*
    5. class DurationPatternValidator implements ConstraintValidator<DurationPattern, CharSequence> {
    6. @Override
    7. boolean isValid(
    8. @Nullable CharSequence value,
    9. @NonNull AnnotationValue<DurationPattern> annotationMetadata,
    10. @NonNull ConstraintValidatorContext context) {
    11. return value == null || value.toString() ==~ /^PT?[\d]+[SMHD]{1}$/
    12. }
    13. }
    1. import io.micronaut.core.annotation.AnnotationValue
    2. import io.micronaut.validation.validator.constraints.*
    3. class DurationPatternValidator : ConstraintValidator<DurationPattern, CharSequence> {
    4. override fun isValid(
    5. value: CharSequence?,
    6. annotationMetadata: AnnotationValue<DurationPattern>,
    7. context: ConstraintValidatorContext): Boolean {
    8. return value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$".toRegex())
    9. }

    In addition to the following requirements:

    • Define a META-INF/services/io.micronaut.validation.validator.constraints.ConstraintValidator file that references the class.

    • The class should be public and feature a zero argument public constructor

    • The class should be placed on the annotation processor classpath of the project that is to be validated.