Criando Annotation Personalizada com Hibernate Validator em 3 passos

Nesse artigo vamos ensinar como você criar uma annotation personalizada, para validar seus objetos, utilizando a biblioteca Hibernate Validator.

O Hibernate Validator é uma poderosa biblioteca muito utilizada no mundo Java que permite a validação de objetos, de forma simples e elegante. Essa biblioteca já nos entrega diversas anotações prontas que facilitam a vida do desenvolvedor mas o seu poder vai além disso, ele nos permite criar nossas próprias annotations para atender requisitos específicos de validação.

Passo 1: Criando a Annotation Personalizada

Nosso primeiro passo será definir a nossa própria annotation, vamos criar um exemplo simples onde vamos validar a idade de um funcionário que será cadastrado na nossa plataforma, a regra de negócio será bem simples para que seja possível o cadastro o nosso funcionário tem que ter a idade legal, ou seja ser maior de 18 anos.

A implementação para isso será bem simples:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import com.artefatox.annotationsvalidation.validation.annotation.impl.LegalAgeImpl;

@Documented
@Constraint(validatedBy = LegalAgeImpl.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface LegalAge {

	String message() default "Idade inválida";
	
	Class<?>[] groups() default {};
	
	Class<? extends Payload>[] payload() default {};
}
Java

Vamos explicar cada passo dessa implementação:

  • @Documented: Indica que essa anotação será documentada via javadoc.
  • @Constraint: Com essa anotação vamos indicar qual será a classe responsável pela implementação da lógica de validação.
  • @Target: Com essa anotação vamos definir onde nossa anotação pode ser utilizada, nesse caso aqui, pode ser utilizada em propriedades ou em parâmetros.
  • @Retention: Essa anotação define em qual tempo de execução será utilizada, nesse caso estamos definindo que é em tempo de execução.
  • String message(): Esse método será o responsável por mostrar a mensagem padrão de erro.

Passo 2: Criando o validador (implementação da validação)

Agora que temos a annotation criada temos que criar uma classe que fará a validação de fato, e que será referenciada na anotação @Constraint da nossa annotation personalizada.

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.artefatox.annotationsvalidation.validation.annotation.LegalAge;

public class LegalAgeImpl implements ConstraintValidator<LegalAge, Integer> {

	@Override
	public void initialize(LegalAge constraintAnnotation) {
		// TODO Auto-generated method stub
		ConstraintValidator.super.initialize(constraintAnnotation);
	}
	
	@Override
	public boolean isValid(Integer value, ConstraintValidatorContext context) {
		if(value >= 18) {
			return true;
		} else {
			return false;
		}
	}

}
Java

Veja que em questão de código é bem simples, nossa classe precisa implementar a interface ConstraintValidator que recebe dois tipos, o primeiro é a sua própria annotation e o segundo o tipo do valor que será validado, essa interface nos força a implementar o método isValid, é dentro dele que vamos colocar a lógica de validação.

A implementação do método initialize não é obrigatória, ele serve para casos em que você precisa fazer uma inicialização antes da validação em si, um exemplo prático para isso é casos em que você precisaria recuperar informações de um outro lugar, por exemplo o application.properties caso esteja utilizando algum framework da web.

Passo 3: Utilizando a Annotation Personalizada

Agora vamos fazer o uso da nossa annotation, para exemplificar construímos uma chamada rest simples com o framework Spring Boot, e no recebimento do body da requisição vamos faze a validação já na camada de controller.

Implementação da entidade

import javax.validation.constraints.Size;

import com.artefatox.annotationsvalidation.validation.annotation.LegalAge;

import lombok.Data;

@Data
public class Employee {

	@Size(min = 3, max = 55)
	private String name;
	
	@LegalAge
	private Integer age;
	
}
Java

Veja que estamos anotando a propriedade age com a nossa annotation personalizada, e também estamos utilizando uma notação padrão da biblioteca para validar o nome.

Implementação do controller

import javax.validation.Valid;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.artefatox.annotationsvalidation.entity.Employee;


@RestController
@RequestMapping("/employees")
public class EmployeeController {

	@PostMapping
	public ResponseEntity<?> register(@Valid @RequestBody Employee employee) {
		return ResponseEntity.status(HttpStatus.CREATED)
				.body("Registration completed successfully for " + employee.getName());
	}
}
Java

O Spring já possui alta compatibilidade com o hibernate validation, então veja que para validar a nosso objeto no controller precisamos apenas anotar o parâmetro com @Valid, agora podemos executar nossa aplicação e ver o funcionamento.

Execução dos testes

Vamos fazer duas requisições e validar a saída:

Requisição 1: Com os valores válidos:

curl --request POST \
  --url http://localhost:8080/employees \
  --header 'Content-Type: application/json' \
  --data '{
	"name": "Maria",
	"age": 18
}'
Terminal

E nossa saída será:

Registration completed successfully for Maria
Terminal

Requisição 2: Com a idade inválida:

curl --request POST \
  --url http://localhost:8080/employees \
  --header 'Content-Type: application/json' \
  --data '{
	"name": "Maria",
	"age": 17
}'
Terminal

E com esse request a saída será:

{"timestamp":"2023-10-25T14:49:08.206+00:00","status":400,"error":"Bad Request","trace":"org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> com.artefatox.annotationsvalidation.controller.EmployeeController.register(com.artefatox.annotationsvalidation.entity.Employee): [Field error in object 'employee' on field 'age': rejected value [17]; codes [LegalAge.employee.age,LegalAge.age,LegalAge.java.lang.Integer,LegalAge]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employee.age,age]; arguments []; default message [age]]; default message [Idade inválida]] \n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:555)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:623)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:928)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1794)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\n","message":"Validation failed for object='employee'. Error count: 1","errors":[{"codes":["LegalAge.employee.age","LegalAge.age","LegalAge.java.lang.Integer","LegalAge"],"arguments":[{"codes":["employee.age","age"],"arguments":null,"defaultMessage":"age","code":"age"}],"defaultMessage":"Idade inválida","objectName":"employee","field":"age","rejectedValue":17,"bindingFailure":false,"code":"LegalAge"}],"path":"/employees"}
Terminal

Você também pode tentar fazer o teste enviando também o nome com menos de 3 caracteres ou mais de 55, que também retornará o erro.

Encontre mais conteúdo sobre java

Mauricio Lima
Mauricio Lima

Bacharel em Ciência da Computação, profissional dedicado ao desenvolvimento de software e entusiasta da tecnologia.

Artigos: 65