6.15 Data Validation
It is easy to validate incoming data with Micronaut controllers using Validation Advice.
First, add the Hibernate Validator configuration to your application:
implementation("io.micronaut.beanvalidation:micronaut-hibernate-validator")
<dependency>
<groupId>io.micronaut.beanvalidation</groupId>
<artifactId>micronaut-hibernate-validator</artifactId>
</dependency>
We can validate parameters using javax.validation
annotations and the Validated annotation at the class level.
Example
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;
import javax.validation.constraints.NotBlank;
import java.util.Collections;
@Validated (1)
@Controller("/email")
public class EmailController {
@Get("/send")
public HttpResponse send(@NotBlank String recipient, (2)
@NotBlank String subject) { (2)
return HttpResponse.ok(Collections.singletonMap("msg", "OK"));
}
}
Example
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import javax.validation.constraints.NotBlank
@Validated (1)
@Controller("/email")
class EmailController {
@Get("/send")
HttpResponse send(@NotBlank String recipient, (2)
@NotBlank String subject) { (2)
HttpResponse.ok(msg: "OK")
}
}
Example
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import javax.validation.constraints.NotBlank
@Validated (1)
@Controller("/email")
open class EmailController {
@Get("/send")
open fun send(@NotBlank recipient: String, (2)
@NotBlank subject: String): HttpResponse<*> { (2)
return HttpResponse.ok(mapOf("msg" to "OK"))
}
}
1 | Annotate controller with Validated |
2 | subject and recipient cannot be blank. |
If a validation error occurs a javax.validation.ConstraintViolationException
is thrown. By default, the integrated io.micronaut.validation.exception.ConstraintExceptionHandler
handles the exception, leading to a behaviour as shown in the following test:
Example Test
@Test
public void testParametersAreValidated() {
HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () ->
client.toBlocking().exchange("/email/send?subject=Hi&recipient="));
HttpResponse<?> response = e.getResponse();
assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());
response = client.toBlocking().exchange("/email/send?subject=Hi&recipient=me@micronaut.example");
assertEquals(HttpStatus.OK, response.getStatus());
}
Example Test
def "test parameter validation"() {
when:
client.toBlocking().exchange('/email/send?subject=Hi&recipient=')
then:
def e = thrown(HttpClientResponseException)
def response = e.response
response.status == HttpStatus.BAD_REQUEST
when:
response = client.toBlocking().exchange('/email/send?subject=Hi&recipient=me@micronaut.example')
then:
response.status == HttpStatus.OK
}
Example Test
"test params are validated"() {
val e = shouldThrow<HttpClientResponseException> {
client.toBlocking().exchange<Any>("/email/send?subject=Hi&recipient=")
}
var response = e.response
response.status shouldBe HttpStatus.BAD_REQUEST
response = client.toBlocking().exchange<Any>("/email/send?subject=Hi&recipient=me@micronaut.example")
response.status shouldBe HttpStatus.OK
}
To use your own ExceptionHandler
to handle the constraint exceptions, annotate it with @Replaces(ConstraintExceptionHandler.class)
Often you may want to use POJOs as controller method parameters.
import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.NotBlank;
@Introspected
public class Email {
@NotBlank (1)
String subject;
@NotBlank (1)
String recipient;
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getRecipient() {
return recipient;
}
public void setRecipient(String recipient) {
this.recipient = recipient;
}
}
import io.micronaut.core.annotation.Introspected
import javax.validation.constraints.NotBlank
@Introspected
class Email {
@NotBlank (1)
String subject
@NotBlank (1)
String recipient
}
import io.micronaut.core.annotation.Introspected
import javax.validation.constraints.NotBlank
@Introspected
open class Email {
@NotBlank (1)
var subject: String? = null
@NotBlank (1)
var recipient: String? = null
}
1 | You can use javax.validation annotations in your POJOs. |
Annotate your controller with Validated, and annotate the binding POJO with @Valid
.
Example
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;
import javax.validation.Valid;
import java.util.Collections;
@Validated (1)
@Controller("/email")
public class EmailController {
@Post("/send")
public HttpResponse send(@Body @Valid Email email) { (2)
return HttpResponse.ok(Collections.singletonMap("msg", "OK"));
}
}
Example
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import javax.validation.Valid
@Validated (1)
@Controller("/email")
class EmailController {
@Post("/send")
HttpResponse send(@Body @Valid Email email) { (2)
HttpResponse.ok(msg: "OK")
}
}
Example
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import javax.validation.Valid
@Validated (1)
@Controller("/email")
open class EmailController {
@Post("/send")
open fun send(@Body @Valid email: Email): HttpResponse<*> { (2)
return HttpResponse.ok(mapOf("msg" to "OK"))
}
}
1 | Annotate the controller with Validated |
2 | Annotate the POJO to validate with @Valid |
Validation of POJOs is shown in the following test:
@Test
public void testPojoValidation() {
HttpClientResponseException e = assertThrows(HttpClientResponseException.class, () -> {
Email email = new Email();
email.subject = "Hi";
email.recipient = "";
client.toBlocking().exchange(HttpRequest.POST("/email/send", email));
});
HttpResponse<?> response = e.getResponse();
assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());
Email email = new Email();
email.subject = "Hi";
email.recipient = "me@micronaut.example";
response = client.toBlocking().exchange(HttpRequest.POST("/email/send", email));
assertEquals(HttpStatus.OK, response.getStatus());
}
def "invoking /email/send parse parameters in a POJO and validates"() {
when:
Email email = new Email(subject: 'Hi', recipient: '')
client.toBlocking().exchange(HttpRequest.POST('/email/send', email))
then:
def e = thrown(HttpClientResponseException)
def response = e.response
response.status == HttpStatus.BAD_REQUEST
when:
email = new Email(subject: 'Hi', recipient: 'me@micronaut.example')
response = client.toBlocking().exchange(HttpRequest.POST('/email/send', email))
then:
response.status == HttpStatus.OK
}
"test pojo validation" {
val e = shouldThrow<HttpClientResponseException> {
val email = Email()
email.subject = "Hi"
email.recipient = ""
client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))
}
var response = e.response
response.status shouldBe HttpStatus.BAD_REQUEST
val email = Email()
email.subject = "Hi"
email.recipient = "me@micronaut.example"
response = client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))
response.status shouldBe HttpStatus.OK
}
Bean injection is supported in custom constraints with the Hibernate Validator configuration. |