How to Create Your Own Custom Annotation in Spring Boot

Isaac Tonyloi - SWE - Oct 9 - - Dev Community

Annotations are a powerful feature in Java and the Spring framework, allowing you to encapsulate logic or behavior in a clean, reusable way. Spring Boot leverages annotations extensively to simplify the development process. However, there may be times when the built-in annotations don’t fully meet your needs, and that’s where custom annotations come in.

In this article, we’ll walk you through the process of creating your own custom annotation in Spring Boot, which can be used to apply reusable behaviors across your application.

What is a Custom Annotation?

A custom annotation is a developer-defined annotation that can encapsulate specific behaviors or configurations. For example, you might create a custom annotation to perform logging, validate input, or implement security checks without repeating code.

In Spring Boot, custom annotations can be combined with aspect-oriented programming (AOP) to add behaviors such as logging or performance tracking to your methods.

Steps to Create a Custom Annotation

Let’s create a custom annotation that performs logging before a method executes. This example will illustrate the key steps involved in creating, configuring, and applying a custom annotation.

1. Define the Custom Annotation

First, we need to create our annotation. You define a custom annotation using the @interface keyword. Annotations can also have attributes, which allow you to pass parameters to your annotation when you use it.

package com.example.annotations;

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

@Target(ElementType.METHOD)  // Specifies that this annotation can be applied to methods
@Retention(RetentionPolicy.RUNTIME)  // The annotation will be available at runtime
public @interface LogExecutionTime {
}
Enter fullscreen mode Exit fullscreen mode

Here’s what each part of the annotation means:

  • @Target(ElementType.METHOD): Specifies where the annotation can be applied. In this case, it can only be applied to methods.
  • @Retention(RetentionPolicy.RUNTIME): Indicates that the annotation will be available at runtime, which is important for reflection and for aspect-oriented programming (AOP).

2. Implement Aspect for Custom Behavior

Once we have the annotation, we can define the behavior we want to associate with it. In this case, we will use Spring AOP to log the execution time of any method annotated with @LogExecutionTime.

First, you need to add Spring AOP dependency to your pom.xml if it’s not already included:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Now, create an aspect class that will contain the logic that should be applied whenever the annotation is encountered.

package com.example.aspects;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("@annotation(com.example.annotations.LogExecutionTime)")  // Indicates that this applies to the custom annotation
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        Object proceed = joinPoint.proceed();  // Proceed with the method execution

        long executionTime = System.currentTimeMillis() - start;

        logger.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
        return proceed;
    }
}
Enter fullscreen mode Exit fullscreen mode

In this aspect class:

  • @Aspect marks this class as an aspect in Spring.
  • @Around("@annotation(com.example.annotations.LogExecutionTime)") ensures that the advice is applied around any method annotated with @LogExecutionTime.
  • The logExecutionTime method measures the time before and after the method execution and logs the execution time.

3. Apply the Custom Annotation

Now that we have both the annotation and the aspect defined, you can apply the custom annotation to any method in your application. Here’s an example of how to use it:

package com.example.controllers;

import com.example.annotations.LogExecutionTime;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ExampleController {

    @LogExecutionTime  // This will trigger the logging aspect
    @GetMapping("/greet")
    public String greet() {
        return "Hello, world!";
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, whenever the /greet endpoint is hit, the execution time of the greet() method will be logged.

4. Testing the Custom Annotation

Start your Spring Boot application and navigate to the /greet endpoint. If everything is set up correctly, you should see the execution time logged in the console.

Example log output:

2024-10-09 12:34:56 INFO  com.example.aspects.LoggingAspect - String com.example.controllers.ExampleController.greet() executed in 5 ms
Enter fullscreen mode Exit fullscreen mode

5. Additional Features (Optional)

Custom annotations can also have parameters. For example, you could modify the @LogExecutionTime annotation to take a boolean parameter to enable or disable logging dynamically.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
    boolean enabled() default true;
}
Enter fullscreen mode Exit fullscreen mode

And modify the aspect accordingly:

@Around("@annotation(logExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
    if (!logExecutionTime.enabled()) {
        return joinPoint.proceed();
    }
    long start = System.currentTimeMillis();
    Object proceed = joinPoint.proceed();
    long executionTime = System.currentTimeMillis() - start;
    logger.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
    return proceed;
}
Enter fullscreen mode Exit fullscreen mode

Now, when you use the annotation, you can control whether logging should be enabled for that method:

@LogExecutionTime(enabled = false)
public String someMethod() {
    // This method won't be logged
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Creating custom annotations in Spring Boot is a great way to encapsulate reusable logic and behaviors, making your code cleaner and more maintainable. By using Spring AOP, you can easily apply cross-cutting concerns like logging, security, or validation in a consistent manner across your application.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .