Validation
Right now the API accepts an article with an empty title or no text at all. Validation means checking input before it reaches the application logic. The rules are written as Jakarta Validation annotations right on the record's fields, and Micronaut checks them and answers 400 with a clear list of errors when they are broken.
Adding the module
If you did not pick the validation feature in the generator, add it by hand. The command mn feature-diff --features=validation, run in the project folder, shows exactly what to add. pom.xml needs two dependencies:
<!-- pom.xml, inside <dependencies> -->
<dependency>
<groupId>io.micronaut.validation</groupId>
<artifactId>micronaut-validation</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<scope>compile</scope>
</dependency>And an annotation processor in the maven-compiler-plugin settings, next to the existing <path> entries:
<!-- pom.xml, inside <annotationProcessorPaths> -->
<path>
<groupId>io.micronaut.validation</groupId>
<artifactId>micronaut-validation-processor</artifactId>
<version>${micronaut.validation.version}</version>
<exclusions>
<exclusion>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject</artifactId>
</exclusion>
</exclusions>
</path>After changing processors, rebuild the whole project: ./mvnw clean compile.
Rules on the record
// src/main/java/com/example/ArticleForm.java
package com.example;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Serdeable
public record ArticleForm(
@NotBlank @Size(max = 100) String title,
@NotBlank String body) {
}| Annotation | What it checks |
|---|---|
@NotNull | The value is not null |
@NotBlank | The string is not empty and not just spaces |
@Size(min, max) | The length of a string or the size of a list |
@Min, @Max | Bounds of a number |
@Email | Looks like an email address |
Switching it on in the controller
Put @Valid before the method parameter in ArticleController:
// src/main/java/com/example/ArticleController.java
@Post
public HttpResponse<Article> create(@Body @Valid ArticleForm form) {
Article article = articleService.create(form);
return HttpResponse.created(article, URI.create("/articles/" + article.id()));
}@Valid is imported from jakarta.validation. The annotation processor sees it and wraps the method with a check at compile time, so no reflection is needed at runtime.
Trying it out
curl -i -X POST http://localhost:8080/articles \
-H "Content-Type: application/json" \
-d '{"body":""}'HTTP/1.1 400 Bad Request
Content-Type: application/json
{"_links":{"self":[{"href":"/articles","templated":false}]},"_embedded":{"errors":[{"message":"form.body: must not be blank"},{"message":"form.title: must not be blank"}]},"message":"Bad Request"}Each violation is a separate errors element naming the parameter and the field. A correct request still creates the article with a 201 response. The annotations work in services too: @Valid or @NotBlank on a method parameter of any bean switches the check on as well.
Common mistake
The sneakiest mistake is when the rules exist but do not fire: an article with an empty title is saved with a 201 response. There are two causes. Either micronaut-validation-processor is not registered, or the project was not rebuilt after adding it, so the old classes were compiled without the check. Make sure the processor is in <annotationProcessorPaths> and run ./mvnw clean compile. A sign that everything is fine: the class $ArticleController$Definition$Intercepted.class appears in target/classes/com/example/.