Spring AOP Meets Reactive Programming: A Survival Guide

Ritresh Girdhar
4 min readMar 1, 2025

--

Spring AOP in a reactive (WebFlux) environment requires special considerations because AOP in Spring is proxy-based, and most of its traditional use cases assume a synchronous execution flow. However, in a reactive world, execution can be deferred and asynchronous, which makes handling aspects like logging, security, and transactions more challenging.

Photo by Tine Ivanič on Unsplash

If you’re coming from a traditional Spring MVC background and thought, “I’ll just slap an @Transactional or @Around and call it a day,” think again! WebFlux operates on an entirely different paradigm, and so should your AOP strategies.

How Spring AOP Works in WebFlux

The Proxy Mechanism & WebFlux Challenge

Spring AOP uses CGLIB (subclass proxying) or JDK dynamic proxies to intercept method calls. However, in WebFlux, methods often return Mono<T> or Flux<T>, and the actual execution of logic happens inside the reactive pipeline—not at method invocation.

Traditional AOP advice that wraps method execution won’t automatically intercept reactive chain operations. In other words, if you were hoping for AOP magic to just work as usual, surprise!

The Right Way to Apply AOP in a Reactive WebFlux Context

Rather than wrapping method calls, you should apply advice around the entire reactive pipeline. This ensures that the logic executes correctly without disrupting the non-blocking, event-driven nature of WebFlux.

Let’s dive into some practical examples.

Example 1: Logging Aspect for WebFlux (Mono & Flux)

Want to log method execution in WebFlux? Here’s how you can do it properly:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Aspect
@Component
public class ReactiveLoggingAspect {

@Around("@annotation(ReactiveLog)")
public Object logReactiveMethods(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
if (result instanceof Mono) {
return ((Mono<?>) result)
.doOnSubscribe(subscription -> System.out.println("Mono started: " + joinPoint.getSignature()))
.doOnTerminate(() -> System.out.println("Mono completed: " + joinPoint.getSignature()));
} else if (result instanceof Flux) {
return ((Flux<?>) result)
.doOnSubscribe(subscription -> System.out.println("Flux started: " + joinPoint.getSignature()))
.doOnTerminate(() -> System.out.println("Flux completed: " + joinPoint.getSignature()));
}
return result;
}
}

Custom Annotation for Logging

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

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

Applying AOP in a WebFlux Controller

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api")
public class WebFluxController {
@ReactiveLog
@GetMapping("/hello")
public Mono<String> sayHello() {
return Mono.just("Hello, Reactive AOP!")
.map(String::toUpperCase);
}
}

Key Takeaways:

  1. Use @Around Advice to intercept methods returning Mono or Flux.
  2. Apply reactive operators (doOnSubscribe, doOnTerminate) inside the AOP advice.
  3. Avoid blocking calls inside the aspect (e.g., .block(), .toFuture()), as it will break the reactive model.
  4. Works best for logging, monitoring, and metrics in WebFlux.

Example 2: Handling Transactions in WebFlux with MongoDB Using Spring AOP

MongoDB supports reactive transactions when using MongoDB 4.0+ with a replica set. However, unlike traditional relational databases, transactions in MongoDB must be explicitly managed using a reactive ClientSession.

Why Not Just Use @Transactional?

Spring AOP does not natively support MongoDB reactive transactions. You need to explicitly manage them using ReactiveMongoTemplate and ClientSession. If you thought you could just slap @Transactional on a method and be done—well, Houston, we have a problem!

Step 1: Define the Aspect for MongoDB Transactions

import com.mongodb.reactivestreams.client.ClientSession;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Aspect
@Component
public class ReactiveMongoTransactionAspect {
private final ReactiveMongoTemplate mongoTemplate;
private final ReactiveMongoDatabaseFactory mongoDatabaseFactory;
public ReactiveMongoTransactionAspect(ReactiveMongoTemplate mongoTemplate, ReactiveMongoDatabaseFactory mongoDatabaseFactory) {
this.mongoTemplate = mongoTemplate;
this.mongoDatabaseFactory = mongoDatabaseFactory;
}
@Around("@annotation(ReactiveMongoTransactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
return Mono.usingWhen(
mongoDatabaseFactory.getSession(),
session -> {
session.startTransaction();
try {
Object result = joinPoint.proceed();
if (result instanceof Mono) {
return ((Mono<?>) result)
.doOnSuccess(unused -> session.commitTransaction())
.doOnError(e -> session.abortTransaction());
} else if (result instanceof Flux) {
return ((Flux<?>) result)
.doOnComplete(session::commitTransaction)
.doOnError(e -> session.abortTransaction());
} else {
session.abortTransaction();
throw new UnsupportedOperationException("Reactive transactions only support Mono/Flux return types");
}
} catch (Throwable ex) {
session.abortTransaction();
return Mono.error(ex);
}
},
ClientSession::close
);
}
}

Step 2: Create a Custom @ReactiveMongoTransactional Annotation

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

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

Step 3: Apply the Transactional AOP in a Service

import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@ReactiveMongoTransactional
public Mono<User> createUser(String name, String email) {
User user = new User(null, name, email);
return userRepository.save(user);
}
}

Final Thoughts

Spring AOP and WebFlux can peacefully coexist — but only if you handle them correctly. Stick to non-blocking patterns, ensure aspects wrap reactive pipelines, and avoid traditional synchronous assumptions.

As they say in the reactive world: “Don’t block — just go with the flow!” 🚀

Thank you for reading!

--

--

Ritresh Girdhar
Ritresh Girdhar

Written by Ritresh Girdhar

Father || Coder || Engineer || Learner || Reader || Writer || Silent Observer

No responses yet