Spring Boot AOP Tutorial

Watch on YouTube
Spring Boot AOP Tutorial | Java Codeex
Java Codeex

Spring Boot AOP (Aspect-Oriented Programming)

Keep cross-cutting concerns such as logging, security, transactions, and timing separate from business logic.

Simple idea: AOP lets you say “run this behavior around methods that match a rule” instead of copying the same code into every method.

What is AOP?

Some behavior affects many parts of an application. Logging, authorization, transaction management, caching, and performance measurement are called cross-cutting concerns. AOP moves that repeated behavior into an aspect and applies it to selected method executions.

How Spring AOP intercepts a method
ClientCalls service method
ProxyRuns advice
Target beanRuns business logic
ResponseReturns to client

Why use AOP?

Logging, authorization, transactions, auditing, caching, and performance measurement affect many services. These are cross-cutting concerns. AOP centralizes the repeated technical behavior so business methods stay focused on business decisions.

Concern AOP behavior Real-world example
Logging Record method and duration Trace an order request
Security Check an annotation Protect refund approval
Transactions Commit or roll back work Save order and inventory together
Auditing Record important changes Track address updates
Performance Measure execution time Find slow service calls

AOP Terminology

Term Meaning
Aspect A class containing cross-cutting behavior.
Join point A point during execution where behavior could be applied; Spring AOP mainly uses method execution.
Pointcut A rule that selects which join points should be intercepted.
Advice The action that runs before, after, or around the selected method.
Target The original bean containing the business method.
Proxy The Spring-created object that intercepts calls to the target.
Weaving The process of connecting an aspect to target behavior; Spring commonly does this through runtime proxies.

Advice Types

  • @Before runs before the matched method.
  • @AfterReturning runs after a successful return.
  • @AfterThrowing runs when the method throws an exception.
  • @After runs after completion, whether successful or failed.
  • @Around surrounds the method and can decide whether and when to call it.

Advice examples

@Aspect
@Component
class AuditAspect {
    @Before("execution(* com.javacodeex.orders.service..*(..))")
    void beforeServiceCall(JoinPoint joinPoint) {
        log.info("Starting {}", joinPoint.getSignature().toShortString());
    }

    @AfterReturning(
            pointcut = "execution(* com.javacodeex.orders.service..*(..))",
            returning = "result")
    void afterSuccess(JoinPoint joinPoint, Object result) {
        log.info("Completed {}", joinPoint.getSignature().toShortString());
    }

    @AfterThrowing(
            pointcut = "execution(* com.javacodeex.orders.service..*(..))",
            throwing = "error")
    void afterFailure(JoinPoint joinPoint, Throwable error) {
        log.warn("{} failed: {}", joinPoint.getSignature(), error.getMessage());
    }
}

Around advice in detail

Around advice receives a ProceedingJoinPoint. Calling proceed() runs the target method. A finally block runs even when the target throws an exception.

@Around("execution(* com.javacodeex.orders.service..*(..))")
Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.nanoTime();
    try {
        return joinPoint.proceed();
    } catch (Throwable error) {
        log.error("Failure in {}", joinPoint.getSignature(), error);
        throw error;
    } finally {
        long elapsedMs = (System.nanoTime() - start) / 1_000_000;
        log.info("{} took {} ms", joinPoint.getSignature(), elapsedMs);
    }
}
Important: If an around advice does not call proceed(), the target method does not run.

Pointcuts

A pointcut expression defines what should be intercepted. Keep pointcuts narrow and readable so an aspect does not unexpectedly affect unrelated code.

// Any method in the service package
@Pointcut("execution(* com.javacodeex.orders.service..*(..))")
private void serviceMethods() { }

// Only methods marked with an annotation
@Pointcut("@annotation(com.javacodeex.orders.TrackTime)")
private void trackedMethods() { }

The execution expression above matches methods inside the service package. An annotation pointcut matches only methods explicitly marked with a custom annotation.

Join points

A join point is a possible point where an aspect could run. Full AspectJ supports more join point types, but Spring AOP is proxy-based and mainly intercepts method execution on Spring-managed Beans.

How a pointcut selects join points
Bean methodsPossible join points
Pointcut rulePackage or annotation
Selected methodsAdvice runs here

Common pointcut designators

Designator Meaning Example
execution Matches method signatures execution(* service..*(..))
within Matches types or packages within(com.javacodeex.orders.service..*)
@annotation Matches annotated methods @annotation(TrackTime)
&& and || Combines pointcut rules serviceMethods() && trackedMethods()

Logging Aspect Example

@Aspect
@Component
class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("execution(* com.javacodeex.orders.service..*(..))")
    Object logCall(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long time = System.currentTimeMillis() - start;
            log.info("{} completed in {} ms", joinPoint.getSignature(), time);
        }
    }
}

ProceedingJoinPoint represents the intercepted call. proceed() invokes the real method; without it, an around advice can prevent the target method from running.

Real-time logging example

A production logging aspect should record method, status, correlation ID, and duration without logging passwords, tokens, or full request bodies.

@Around("@annotation(com.javacodeex.orders.Logged)")
Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
    String operation = joinPoint.getSignature().toShortString();
    Instant started = Instant.now();
    try {
        Object result = joinPoint.proceed();
        log.info("operation={} status=success durationMs={}",
                operation, Duration.between(started, Instant.now()).toMillis());
        return result;
    } catch (Throwable error) {
        log.warn("operation={} status=failure type={}",
                operation, error.getClass().getSimpleName());
        throw error;
    }
}

Authorization and auditing

Spring Security uses method interception for annotations such as @PreAuthorize. A custom aspect can add an audit event, but it should complement the security filter chain rather than replace it.

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

@Audited
@PreAuthorize("hasRole('MANAGER')")
public void approveRefund(Long orderId) {
    // Protected business operation
}

Caching and performance

Caching and timing are common AOP use cases. Define cache keys and invalidation rules carefully so an aspect does not return stale data.

@Service
class ProductService {
    @Cacheable(cacheNames = "products", key = "#id")
    public Product findById(Long id) {
        return repository.findById(id).orElseThrow();
    }
}

Custom Annotation AOP

Annotations make an aspect opt-in and easier to understand at the call site.

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

@TrackTime
public Report createReport() {
    return reportService.create();
}

Use an annotation pointcut to apply timing only to methods marked with @TrackTime.

AOP and Transactions

@Transactional is a familiar Spring AOP use case. Spring creates a proxy that opens a transaction before the method and commits or rolls it back afterward.

@Service
class OrderService {
    @Transactional
    public void placeOrder(OrderRequest request) {
        orderRepository.save(toOrder(request));
        inventoryService.reserve(request.items());
    }
}
Important proxy rule: A call from one method to another method in the same object may bypass the proxy. Put transactional or secured operations in a separate bean when proxy interception is required.

Proxy limitations and common mistakes

  • Self-invocation: one method calling another method on the same object can bypass the proxy.
  • Objects created with new: Spring cannot apply advice to objects it does not manage.
  • Private and final methods: proxy-based Spring AOP may not intercept them as expected.
  • Broad pointcuts: an overly broad rule can log health checks or sensitive data unexpectedly.
  • Hidden behavior: keep important security and transaction boundaries visible in design and tests.

Enable AOP in Spring Boot

Add the AOP starter to enable the infrastructure required by component-based aspects.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Good Use Cases and Pitfalls

Good use Be careful about
Logging and timing Do not log passwords, tokens, or sensitive payloads.
Authorization checks Make security rules visible and test them.
Transactions Understand proxy boundaries and rollback rules.
Auditing Keep audit data reliable and avoid hidden side effects.
Caching Define keys and invalidation behavior clearly.
Next: Deployment →

© 2026 Java Codeex

Continue learning