
How to create a Custom Annotation in Quarkus Framework?
Natan Ferreira
- 0
- 923
It’s important to perform validations in the system. There are dependencies available for this with a lot of ready-made solutions. For example, in the case of Quarkus, we can use Hibernate Validator, which provides us with some annotations to solve common problems. However, if it’s necessary to perform a validation for which there is no annotation to solve the problem, we can also create our own validation. Next, let’s see an example of how to create a custom annotation.
Creating the Project
The project is an API created using the Quarkus Framework. A very simple way to create it is through the Quarkus website: https://code.quarkus.io/.
It’s also possible to create it via the terminal by following the necessary installations: https://quarkus.io/get-started/
All dependencies used
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Code of the Project
The validation we are going to perform is quite simple. I have created a class User
with the fields name
and email
. We have a collection of names that are prohibited. If a person enters a name contained in the collection, they will receive an error.
package com.natancode.domain;
import lombok.*;
import java.util.Set;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
public class User {
public static final Set<String> FORBIDDEN_NAMES = Set.of("Walter White");
private String name;
private String email;
}
This is the endpoint that receives the user:
package com.natancode.api.resources;
import com.natancode.domain.User;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("users")
public class UserResource {
@POST
@Consumes({MediaType.APPLICATION_JSON})
public Response createUser(User user){
return Response.noContent().build();
}
}
At the moment, we are not validating anything. Next, we will create the validator so that we can use it in the endpoint.
Custom Annotation
We need to create the interface and its implementation.
package com.natancode.validators;
import com.natancode.validators.impl.UserValidatorImpl;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@Constraint(validatedBy = UserValidatorImpl.class)
public @interface UserValidator {
String message() default "Sorry, this name is forbidden";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
- @Documented: Indicates that annotations with a type are to be documented by javadoc and similar tools by default.
- @Retention(RetentionPolicy.RUNTIME): Specifies that this annotation should be retained at runtime.
- @Target({ElementType.PARAMETER}): Indicates that this annotation can only be applied to parameters.
- @Constraint(validatedBy = UserValidatorImpl.class): It is the implementation of the annotation that will perform the validation.
- String message() default “Sorry, this name is forbidden”: It is a default message that will be displayed if there is an invalid parameter.
- Class[] groups() default {}: Specifies the validation groups to which this constraint belongs.
- Class[] payload() default {}: Specifies the payload type associated with the constraint.
Let’s see the implementation:
package com.natancode.validators.impl;
import com.natancode.domain.User;
import com.natancode.validators.UserValidator;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import static com.natancode.domain.User.FORBIDDEN_NAMES;
public class UserValidatorImpl implements ConstraintValidator<UserValidator, User> {
@Override
public void initialize(UserValidator constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(User user, ConstraintValidatorContext constraintValidatorContext) {
return !FORBIDDEN_NAMES.contains(user.getName());
}
}
When implementing ConstraintValidator, we need to implement its methods. In the isValid method, we put the validation logic. In this case, it checks if the provided name is not contained in the collection. If it’s not, then it’s considered valid; otherwise, it will throw an error.
For the validation to occur, we need to use the annotation in the Endpoint.
package com.natancode.api.resources;
import com.natancode.domain.User;
import com.natancode.validators.UserValidator;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("users")
public class UserResource {
@POST
@Consumes({MediaType.APPLICATION_JSON})
public Response createUser(@UserValidator User user){
return Response.noContent().build();
}
}
We use the @UserValidator annotation that we created.
Let’s see it working. To run the API, we need to enter the following command:
mvn quarkus:dev
I used postman to make the request.


That was a simple example. However, before creating a custom annotation, check if the validation you need already exists in the validation dependency you’re using. This helps avoid unnecessary work.
Author
-
I am a seasoned Full Stack Software Developer with 8+ years of experience, including 6+ years specializing in Java with Spring and Quarkus. My core expertise lies in developing robust RESTful APIs integrated with Cosmos Db, MySQL, and cloud platforms like Azure and AWS. I have extensive experience designing and implementing microservices architectures, ensuring performance and reliability for high-traffic systems. In addition to backend development, I have experience with Angular to build user-friendly interfaces, leveraging my postgraduate degree in frontend web development to deliver seamless and responsive user experiences. My dedication to clean and secure code led me to present best practices to my company and clients, using tools like Sonar to ensure code quality and security. I am a critical thinker, problem solver, and team player, thriving in collaborative environments while tackling complex challenges. Beyond development, I share knowledge through my blog, NatanCode, where I write about Java, Spring, Quarkus, databases, and frontend development. My passion for learning and delivering innovative solutions drives me to excel in every project I undertake.