Create Your Own Custom Annotation In Spring Boot

Amila Iroshan
The Fresh Writes
Published in
7 min readMar 12, 2023

Introduction

In this article I’ll explain the way of create your own custom annotation with Spring Boot application.

What is annotation in Spring Boot ?

Annotations has been introduced with JDK5 onwards and it is alternative to XML and Java properties configuration. It provides some essential metadata information into our source code.
These annotations can then be processed at compile time by the compiler tools or during at runtime via Java Reflection.

Why we use annotation with Spring Boot ?

It is a very simple and provide concise way to supply information about a program. Spring Boot reduces the time it takes to develop applications with help of annotations. This capability helps you create standalone applications with less or almost no xml configuration overhead.

Description:

Annotations are created by using @ sign, followed by the keyword interface, and followed by annotation name as shown in the below example.

public @interface EnableRestCallLogs{

}

Members can be decleared looks like methods. The example defines two members called nameOfAnnotation and typeOfAnnotation. These members does not have anyimplementation.

public @interface EnableRestCallLogs{
String nameOfAnnotation();
String typeOfAnnotation();

}

While implement annotation we have to specify some special metadata with it. Those are as below,

Retention policy(@Retention)

It specifies the Retention policy. A retention policy determines at what time annotation should be discarded. Mainly java defined 3 types of retention policies. Those are SOURCE, CLASS and RUNTIME.
The SOURCE policy will be retained only with source code, and discarded during compile time. It effected on before compile the source code.
Besides that very common java annotations like @Override, @Deprecated, @FunctionalInterface,@SuppressWarnings also uses retention policy as SOURCE.
Then retention policy CLASS will be retained until compiling the code, and discarded during runtime.
Retention policy RUNTIME will be available to the JVM through runtime.If we not specify the retention policy the default retention policy type is CLASS.
We can specify the retention policy by using java built-in annotation @Retention, and we have to pass the retention policy type.

@Retention(RetentionPolicy.RUNTIME)

Target(@Target)

@Target annotation definition defines where to apply the annotation .
It takes ElementType enumeration as its only argument.
The ElementType enumeration is a constant which specifies the type of the program element declaration (class, interface, constructor, etc.) to which the annotation can be applied.

Type -> Class, interface or enumeration
Field -> Field
Method -> Method
Constructor -> Constructor
Local_Variable -> Local variable
Annotation_Type -> Annotation Type
Package -> PACKAGE
Type_Parameter -> Type Parameter
Parameter -> Formal Parameter

@Target({ElementType.METHOD, ElementType.PACKAGE})

@Documented

The use of @Documented annotation in the code enables tools like Javadoc to process it and include the annotation type information in the generated document.

Let’s start the implementation of our custom annotation

In this demo application I’m going to create custom annotation which used to get essential information from Rest Call.
My custom annotation called @EnableRestCallLogs and it trace/log the
Signature of the method, ExecutionTime , RequestBody and ResponseBody.

Besides that I’m using AOP (Aspect Orient Programming) declarative programming to adding behavior to existing code without modifying that code. In brief AOP (Aspect-Oriented Programming) is a programming pattern that increases modularity by allowing the separation of the cross-cutting concern. These cross-cutting concerns are different from the main business logic.
NOTE : In here I’m not going to deep dive to AOP.

Advice: The advice is an action that we take either before or after the method execution.
Aspect: An aspect is a module that encapsulates advice and pointcuts and provides cross-cutting
Join point: A join point is a point in the application where we apply an AOP aspect.
Pointcut: A pointcut is an expression that selects one or more join points where advice is executed. We can define pointcuts using expressions or patterns.

Set up the project

Create spring boot application.
— Navigate to https://start.spring.io.

  • Choose either Gradle or Maven as build tool. In here I’m using Maven and Java 18.

    This is my dependencies on pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.annotation</groupId>
<artifactId>CustomAnnotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>CustomAnnotation</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

This is my project structure

Project Structure

STEP 1: Create an annotation interface called as @EnableRestCallLogs

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableRestCallLogs {

}

STEP 2: Create an Aspect

@Aspect
@Component
public class EnableRestCallLogAspect {
@Around("@annotation(EnableRestCallLogs)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

long initTime = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - initTime;
System.out.println("============================================================================================================");
System.out.println("Method Signature is : "+joinPoint.getSignature() );
System.out.println("Method executed in : " + executionTime + "ms");
System.out.println("Input Request: " + joinPoint.getArgs()[0]);
System.out.println("Output Response : " + proceed);
return proceed;
}
}

Use the annotation @Aspect to let know Spring that this is an Aspect class. Then use with @Component ,spring identifies that the class as a Spring bean. @Around annotation has a point cut argument. Our pointcut just says, that apply this advice any method which is annotated with @EnableRestCallLogs. This method accept the ProceedingJoinPoint type and should return Object data type.The ProceedingJoinPoint gives access to the method and we can retrieve the parameters using getArgs() method.
You can get the response by calling joinPoint.proceed() .

STEP 3: Create DTO (Data transfer objects) as request and response pojo classes.

public class InputRequestInfo implements Serializable {
private String request_type;
private Map<String, Object> request_params;

public String getRequest_type() {
return request_type;
}
public void setRequest_type(String request_type) {
this.request_type = request_type;
}
public Map<String, Object> getRequest_params() {
return request_params;
}
public void setRequest_params(Map<String, Object> request_params) {
this.request_params = request_params;
}
@Override
public String toString() {
return "InputRequestInfo [request_type=" + request_type + ", request_params=" + request_params + "]";
}
}
public class OutputResponseInfo implements Serializable{

private HttpStatus response_state;
private Object payload;

@Override
public String toString() {
return "OutputResponseInfo [response_state=" + response_state + ", payload=" + payload + "]";
}

public HttpStatus getResponse_state() {
return response_state;
}
public void setResponse_state(HttpStatus response_state) {
this.response_state = response_state;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}

STEP 4: Create Rest End point (GET and POST ) where we plug our custom annotation.

@RestController
@RequestMapping("/api")
public class DemoRestController {

@GetMapping("/testGet/{name}")
@ResponseStatus(HttpStatus.OK)
@EnableRestCallLogs
public OutputResponseInfo testGetEndPoint(@PathVariable String name) {

OutputResponseInfo result=new OutputResponseInfo();
result.setResponse_state(HttpStatus.OK);
Map<String, Object> hm= new HashMap<>();
hm.put("Id", Integer.valueOf(1));
hm.put("Input_Name", String.valueOf(name));
result.setPayload(hm);
return result;
}
@PostMapping("/testPost")
@ResponseStatus(HttpStatus.CREATED)
@EnableRestCallLogs
public OutputResponseInfo testPostEndPoint(@RequestBody InputRequestInfo inputRequestInfo) {
OutputResponseInfo result=new OutputResponseInfo();
result.setResponse_state(HttpStatus.CREATED);
Map<String, Object> hm= new HashMap<>();
hm.put("Payload", inputRequestInfo);
result.setPayload(hm);
return result;
}
}

STEP 5 : Run & Check result

Run Spring Boot application with command: mvn spring-boot:run

Get Request and Responce
Trace the Get Request Information
Post Request
Trace the Post Request Information

That's it guys,,,

Thank you for read this article and If you like this article, do follow and clap 👏🏻.Happy coding, Cheers !!😊😊

You can find the complete source code of this example on GitHub

Thanks for reading.Happy learning 😄

Do support our publication by following it

--

--

Amila Iroshan
The Fresh Writes

Software Engineer | Open Source Contributor | Tech Enthusiast