Spring AOP
AspectJ
Async Programming
HttpServletRequest
Java Annotations

Async Aspect AfterReturnung. My method has HttpServletRequest object as method argument, but able to access it my aop method

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

If an @AfterReturning advice needs request information, it can access HttpServletRequest while it is still running in the original servlet thread. The problem starts when you mix that with @Async, because request context is thread-bound and does not automatically travel to the async executor thread.

Why It Works in the Original Advice

If the intercepted method already has HttpServletRequest as an argument, Spring AOP can bind that argument directly in the advice.

java
1@Aspect
2@Component
3public class AuditAspect {
4
5    @AfterReturning(
6        pointcut = "execution(* com.example.service.UserService.create(..)) && args(request, ..)",
7        returning = "result"
8    )
9    public void afterCreate(HttpServletRequest request, Object result) {
10        System.out.println(request.getRequestURI());
11        System.out.println(result);
12    }
13}

This works because the advice runs in the same request-handling thread where the servlet request is still valid.

Why It Breaks with @Async

HttpServletRequest and RequestContextHolder are tied to the servlet thread. When you hand work to an async executor:

  • a different thread runs the work
  • request-scoped context is no longer guaranteed
  • the original request may already be completed

That means code like this is unsafe:

java
1@Async
2public void doAuditLater(HttpServletRequest request) {
3    System.out.println(request.getHeader("User-Agent"));
4}

Even if it appears to work in some tests, it is the wrong contract. The servlet request object is not a stable async payload.

The Safe Pattern: Copy What You Need

Extract the required request data in the original thread and pass only plain values to async code.

java
public record AuditEvent(String uri, String ip, String userAgent, String action) {}
java
1@Service
2public class AuditService {
3
4    @Async
5    public void writeAudit(AuditEvent event) {
6        System.out.println("async audit = " + event);
7    }
8}
java
1@Aspect
2@Component
3public class AuditAspect {
4
5    private final AuditService auditService;
6
7    public AuditAspect(AuditService auditService) {
8        this.auditService = auditService;
9    }
10
11    @AfterReturning(
12        pointcut = "execution(* com.example.service.UserService.create(..)) && args(request, ..)",
13        returning = "result"
14    )
15    public void afterCreate(HttpServletRequest request, Object result) {
16        AuditEvent event = new AuditEvent(
17                request.getRequestURI(),
18                request.getRemoteAddr(),
19                request.getHeader("User-Agent"),
20                "CREATE_USER"
21        );
22        auditService.writeAudit(event);
23    }
24}

This is the reliable design. The async method gets immutable data, not a thread-bound servlet object.

What About RequestContextHolder

RequestContextHolder is sometimes used when the controller method does not expose the request directly.

java
1ServletRequestAttributes attrs =
2        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
3
4if (attrs != null) {
5    HttpServletRequest request = attrs.getRequest();
6    String traceId = request.getHeader("X-Trace-Id");
7}

This is fine only while you are still in the request thread. It does not solve async propagation by itself.

If you need correlation data in async work, copy that data explicitly or use dedicated context-propagation techniques for logging and tracing.

Keep AOP and Async Responsibilities Separate

A good design split is:

  • aspect extracts request-related metadata
  • async service persists or publishes the audit event

That keeps the aspect lightweight and avoids mixing thread-bound servlet concerns with background processing concerns.

It also makes testing easier because the async service can accept a simple DTO rather than a servlet object.

Executor Configuration Still Matters

Async safety is not only about the request object. You also want a controlled executor:

java
1@Bean
2public ThreadPoolTaskExecutor auditExecutor() {
3    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
4    executor.setCorePoolSize(2);
5    executor.setMaxPoolSize(8);
6    executor.setQueueCapacity(100);
7    executor.setThreadNamePrefix("audit-");
8    executor.initialize();
9    return executor;
10}

This does not propagate servlet request state, but it gives you predictable async behavior and observability.

Common Pitfalls

Passing HttpServletRequest directly into async methods is the main mistake. It couples background work to a request object whose lifetime is already ending.

Assuming RequestContextHolder will behave the same on executor threads leads to null or stale context problems.

Trying to do too much in the aspect itself makes it harder to reason about failure behavior and thread boundaries.

If the pointcut does not bind arguments correctly, the advice may never receive the request you think it will.

Summary

  • 'HttpServletRequest is safe to read in the original advice thread, not as an async payload.'
  • '@Async runs on a different thread, so request context does not automatically follow.'
  • Extract only the values you need and pass them in a DTO or record.
  • Use RequestContextHolder only in the request thread unless you add explicit context propagation.
  • Keep the aspect responsible for extraction and the async service responsible for background work.

Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions