Spring AOP: Advice Types
Learn the full family of Spring AOP advice annotations, @Before, @After, @AfterReturning, @AfterThrowing, and @Around, with a practical timing example.
Introduction
In the previous lesson you wrote a @Before advice that ran ahead of a method call. Spring AOP actually offers five distinct advice types, each controlling exactly when your code runs relative to the method it targets. Picking the right one makes your aspects precise and easy to reason about.
- All five Spring AOP advice types
- How @AfterReturning and @AfterThrowing differ from plain @After
- How @Around gives you full control with ProceedingJoinPoint
- How to build a method-timing aspect
The Five Advice Types
| Annotation | Runs |
|---|---|
| @Before | Before the matched method executes |
| @After | After the matched method finishes, whether it succeeded or threw |
| @AfterReturning | After the matched method returns successfully |
| @AfterThrowing | After the matched method throws an exception |
| @Around | Wraps the matched method entirely; you control whether it runs at all |
@Before and @After
@Aspect@Componentpublic class LifecycleLoggingAspect {
@Before("execution(* com.example.app.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before: " + joinPoint.getSignature().getName()); }
@After("execution(* com.example.app.service.*.*(..))") public void logAfter(JoinPoint joinPoint) { System.out.println("After: " + joinPoint.getSignature().getName()); }}Click Run to see what this code prints.
@AfterReturning and @AfterThrowing
@AfterReturning lets you inspect the value a method returned, while @AfterThrowing lets you react to an exception it threw. Only one of the two will run for any given call.
@Aspect@Componentpublic class ResultAspect {
@AfterReturning( pointcut = "execution(* com.example.app.service.*.*(..))", returning = "result" ) public void logResult(JoinPoint joinPoint, Object result) { System.out.println(joinPoint.getSignature().getName() + " returned: " + result); }
@AfterThrowing( pointcut = "execution(* com.example.app.service.*.*(..))", throwing = "ex" ) public void logException(JoinPoint joinPoint, Exception ex) { System.out.println(joinPoint.getSignature().getName() + " threw: " + ex.getMessage()); }}@Around: The Most Powerful Advice
@Around advice wraps the entire method call. Its method receives a ProceedingJoinPoint, and it must call proceed() explicitly to let the original method run, which means it can also choose to skip it, change its arguments, or alter the returned value.
@Aspect@Componentpublic class SecurityAspect {
@Around("execution(* com.example.app.service.AdminService.*(..))") public Object checkAccess(ProceedingJoinPoint joinPoint) throws Throwable { boolean isAuthorized = false; // replace with a real permission check
if (!isAuthorized) { throw new SecurityException("Access denied to " + joinPoint.getSignature().getName()); }
return joinPoint.proceed(); }}Practical Example: Timing Aspect
A very common use of @Around advice is measuring how long a method took to run, without touching the method's own code.
@Aspect@Componentpublic class PerformanceAspect {
@Around("execution(* com.example.app.service.*.*(..))") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start; System.out.println(joinPoint.getSignature() + " executed in " + elapsed + "ms");
return result; }}Click Run to see what this code prints.
Common Mistakes
- Forgetting to call proceed() inside @Around advice, which silently prevents the original method from ever running.
- Using @Around when a simpler @Before or @After would express the intent more clearly.
- Not declaring the correct "throwing" parameter type in @AfterThrowing, so the advice never matches the exception actually thrown.
- Assuming @After behaves like @AfterReturning; @After runs regardless of whether the method succeeded or threw.
Best Practices
- Reach for the most specific advice type that expresses your intent: @AfterReturning for results, @AfterThrowing for errors.
- Reserve @Around for cases that truly need to control whether or how the method executes, such as timing, caching, or security checks.
- Always propagate the original return value from @Around advice unless you intentionally mean to change it.
- Keep pointcut expressions in one place, such as a constant or a shared pointcut method, if several aspects reuse the same one.
Frequently Asked Questions
The target method never executes. Any code after the advice runs, but callers will not get the real method's result, which usually surfaces as a silent bug rather than an error.
Yes. Spring resolves them in a defined order (around advice wraps the others), and multiple aspects can target the same join point at once.
Yes, @After always runs after the method completes, whether it returned normally or threw, similar to a finally block.
Key Takeaways
- Spring AOP offers five advice types: @Before, @After, @AfterReturning, @AfterThrowing, and @Around.
- @Before and @After run relative to the method call regardless of its outcome.
- @AfterReturning and @AfterThrowing are outcome-specific.
- @Around gives full control over the method call through ProceedingJoinPoint, but requires calling proceed() explicitly.
- A timing or logging aspect is a classic, practical use of @Around advice.
Summary
With all five advice types in your toolkit, you can express almost any cross-cutting behavior precisely and cleanly. Having covered configuration, dependency injection, and AOP, you are ready to move into a new area of the framework: Spring MVC, starting with a high-level look at how it structures web applications.