6.14 Data Validation
It is easy to validate incoming data with Micronaut’s controllers with the 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.context.annotation.Requires;
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.context.annotation.Requires;
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")
class EmailController {
@Get("/send")
HttpResponse send(@NotBlank String recipient, (2)
@NotBlank String subject) { (2)
HttpResponse.ok(Collections.singletonMap("msg", "OK"))
}
}
Example
import io.micronaut.context.annotation.Requires
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")
open class EmailController {
@Get("/send")
open fun send(@NotBlank recipient: String, (2)
@NotBlank subject: String): HttpResponse<*> { (2)
return HttpResponse.ok(Collections.singletonMap("msg", "OK"))
}
}
1 | Annotate controller with Validated |
2 | subject and recipient cannot be blank. |
If a validation error occurs a javax.validation.ConstraintViolationException
will be thrown. By default, the integrated io.micronaut.validation.exception.ConstraintExceptionHandler
is used to handle 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
}
If you want your own ExceptionHandler
to handle the constraint exceptions, you need to annotate it with @Replaces(ConstraintExceptionHandler.class)
Often, you may want to use POJOs as controller method parameters.
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.docs.datavalidation.pogo;
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;
}
}
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.docs.datavalidation.pogo;
import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.NotBlank;
@Introspected
class Email {
@NotBlank (1)
String subject;
@NotBlank (1)
String recipient;
}
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.docs.datavalidation.pogo
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. |
You need to annotate your controller with Validated. Also, you need to annotate the binding POJO with @Valid
.
Example
import io.micronaut.context.annotation.Requires;
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.context.annotation.Requires;
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")
class EmailController {
@Post("/send")
HttpResponse send(@Body @Valid Email email) { (2)
HttpResponse.ok(Collections.singletonMap("msg", "OK"))
}
}
Example
import io.micronaut.context.annotation.Requires
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")
open class EmailController {
@Post("/send")
open fun send(@Body @Valid email: Email): HttpResponse<*> { (2)
return HttpResponse.ok(Collections.singletonMap("msg", "OK"))
}
}
1 | Annotate controller with Validated |
2 | Annotate the POJO which you wish to validate with @Valid |
The validation of POJOs is shown in the following test:
public void testPoJoValidation() {
HttpClientResponseException e = Assertions.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()
email.subject = 'Hi'
email.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()
email.subject = 'Hi'
email.recipient = 'me@micronaut.example'
response = client.toBlocking().exchange(HttpRequest.POST('/email/send', email))
then:
response.status == HttpStatus.OK
}
"test poko 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. |