Imperative vs. Reactive API in Spring Boot
September 19, 2024 | by Cícero Fabio
In Spring Boot, APIs can be built using two main programming paradigms: Imperative and Reactive. While both approaches can achieve similar goals, the way they handle execution, data flow, and system resources differs significantly.
Imperative API
The imperative model is synchronous and blocking. When a request is made, the thread that handles the request waits for the task to complete before moving on to the next task. This is the traditional way of writing Java applications. It’s easier to reason about but can lead to thread exhaustion under high load.
Key Features:
- Synchronous: Operations are performed in sequence.
- Blocking: Threads wait for the current task to complete.
- Thread-per-request: Each request occupies a thread until completion.
- Simpler: Easier to debug and understand.
Reactive API
The reactive model, on the other hand, is asynchronous and non-blocking. Spring WebFlux, introduced in Spring 5, provides tools to build reactive APIs using the reactive streams specification. Reactive APIs are highly scalable, as they don’t block threads while waiting for resources (e.g., database responses), making them suitable for high-throughput, low-latency applications.
Key Features:
- Asynchronous: Operations are performed independently of the main thread.
- Non-blocking: Threads are not held up while waiting for external resources.
- Event-driven: Built around the concept of events (data streams).
- Scalable: Better performance under high load.
Example: Building a Reactive API with Spring WebFlux and MongoDB
Below is an example of a Reactive API using Spring WebFlux and MongoDB. It demonstrates how to create a repository, service, and controller using reactive programming principles.
1. Dependency Configuration (pom.xml)
Ensure the following dependencies are included in your pom.xml
:
<dependencies>
<!-- Spring Boot WebFlux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Data Reactive MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
</dependencies>
2. Reactive MongoDB Repository
In Spring Data, we define a repository interface for MongoDB. Here, we extend ReactiveMongoRepository
, which provides reactive support.
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Mono;
public interface UserRepository extends ReactiveMongoRepository<User, String> {
Mono<User> findByEmail(String email);
}
3. Service Layer
The service handles business logic and uses the reactive repository to interact with MongoDB. The methods return Mono
or Flux
to handle asynchronous data streams.
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Mono<User> createUser(User user) {
return userRepository.save(user);
}
public Mono<User> getUserByEmail(String email) {
return userRepository.findByEmail(email);
}
public Flux<User> getAllUsers() {
return userRepository.findAll();
}
public Mono<Void> deleteUser(String userId) {
return userRepository.deleteById(userId);
}
}
4. Controller Layer
The controller layer exposes endpoints for the API. Using Spring WebFlux, we define routes that return reactive types (Mono
or Flux
).
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public Mono<User> createUser(@RequestBody User user) {
return userService.createUser(user);
}
@GetMapping("/{email}")
public Mono<User> getUserByEmail(@PathVariable String email) {
return userService.getUserByEmail(email);
}
@GetMapping
public Flux<User> getAllUsers() {
return userService.getAllUsers();
}
@DeleteMapping("/{id}")
public Mono<Void> deleteUser(@PathVariable String id) {
return userService.deleteUser(id);
}
}
5. MongoDB Configuration (Optional)
If MongoDB is not auto-configured, you might need to manually define the connection settings.
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
@Configuration
public class MongoConfig extends AbstractReactiveMongoConfiguration {
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create("mongodb://localhost:27017");
}
@Override
protected String getDatabaseName() {
return "mydatabase";
}
}
Conclusion
While imperative APIs are easier to write and reason about, reactive APIs provide significant performance benefits under high load. They can handle thousands of concurrent requests with a small thread pool, making them ideal for modern microservices and distributed systems. By using Spring WebFlux with MongoDB, you can create highly efficient and scalable reactive APIs.
This example demonstrates a basic CRUD setup, but WebFlux can also handle more complex reactive scenarios, such as streaming data, server-sent events, and WebSockets, all with non-blocking, asynchronous execution.
RELATED POSTS
View all