I have setup validation in Micronaut 4.6.1 and it’s not working for me. The DTO doesn’t get validated. The request passes through to the controller as if the validation annotations don’t exits.
DTO (validation that the properties are not null):
@Data
@AllArgsConstructor
@Introspected
public class TripCreateRequest {
@NotNull
private LocationDto start;
@NotNull
private LocationDto destination;
}
Controller (create endpoint):
@Post
TripCreateResponse createOne(@Body @Valid TripCreateRequest tripCreateRequest) {
Trip trip = new Trip(
new Location(tripCreateRequest.getStart().getLatitude(), tripCreateRequest.getStart().getLongitude()),
new Location(tripCreateRequest.getDestination().getLatitude(), tripCreateRequest.getDestination().getLongitude())
);
trip = this.tripService.createOne(trip);
return TripCreateResponse.from(trip);
}
dependencies (I am using Jackson along with validation):
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-jackson-databind</artifactId>
<scope>compile</scope>
</dependency>
<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>
build (the annotation processors):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- Uncomment to enable incremental compilation -->
<!-- <useIncrementalCompilation>false</useIncrementalCompilation> -->
<annotationProcessorPaths combine.children="append">
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-validation</artifactId>
<version>${micronaut.core.version}</version>
</path>
<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>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amicronaut.processing.group=com.example</arg>
<arg>-Amicronaut.processing.module=demo-m2</arg>
</compilerArgs>
</configuration>
</plugin>
What am I doing wrong? The code acts as if the validation annotations don’t exist.
Trying Serde instead of Jackson didn’t work for me either.
Except for missing
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${micronaut.core.version}</version>
</path>
below annotationProcessorPaths
in POM, code looks OK to me.
Let’s create a minimal POC for this and add some tests:
Controller
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import jakarta.validation.Valid;
@Controller("/trip")
public class TripController {
@Post
String createOne(@Body @Valid TripCreateRequest tripCreateRequest) {
return tripCreateRequest.toString();
}
}
LocationDto (missing from the question)
import io.micronaut.core.annotation.Introspected;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@Introspected
public class LocationDto {
public String name;
}
TripControllerTest
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@MicronautTest
class TripControllerTest {
@Inject
@Client("/trip")
HttpClient client;
@Test
void testCreateOneWithValidRequest_expectOK() {
var validRequest = new TripCreateRequest(
new LocationDto("Oslo"), // start
new LocationDto("Stockholm") // destination
);
var request = HttpRequest.POST("/", validRequest);
var response = client.toBlocking().exchange(request, String.class);
assertEquals(200, response.getStatus().getCode());
assertEquals("TripCreateRequest(start=LocationDto(name=Oslo), destination=LocationDto(name=Stockholm))", response.body());
}
@Test
void testCreateOneWithInvalidRequest_expectBadRequest() {
var invalidRequest = new TripCreateRequest(null, null);
var request = HttpRequest.POST("/", invalidRequest);
var exception = assertThrows(HttpClientResponseException.class, () ->
client.toBlocking().exchange(request, String.class)
);
assertEquals(400, exception.getStatus().getCode());
}
}
Build section in POM
<build>
<plugins>
<plugin>
<groupId>io.micronaut.maven</groupId>
<artifactId>micronaut-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths combine.self="override">
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${micronaut.core.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-validation</artifactId>
<version>${micronaut.core.version}</version>
</path>
<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>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amicronaut.processing.group=com.example</arg>
<arg>-Amicronaut.processing.module=mn-validation-demo</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>