Skip to main content
Advertisement

13.2 Practical WebFlux Usage (WebClient and Handlers)

Explore how to utilize Spring WebFlux in real-world projects, specifically focusing on WebClient for external API calls and detailed implementation examples of Functional Endpoints.

1. Advanced WebClient Usage

WebClient is a non-blocking HTTP client used for communication with external services in a WebFlux environment.

1) Creating a WebClient Instance

You can create an instance in two ways.

// Option 1: Simple creation (create)
WebClient client1 = WebClient.create("http://localhost:8080");

// Option 2: Creation with detailed settings (builder) - Recommended
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}

2) GET / POST Request Examples

@Service
@RequiredArgsConstructor
public class ReactiveApiService {
private final WebClient webClient;

// GET Request: Retrieve a single item
public Mono<UserDto> getUser(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(UserDto.class);
}

// POST Request: Send data
public Mono<UserDto> createUser(UserDto userDto) {
return webClient.post()
.uri("/users")
.bodyValue(userDto)
.retrieve()
.bodyToMono(UserDto.class);
}
}

2. Detailed Functional Endpoints Example

The functional approach clearly separates the business logic (Handler) from path mapping (Router).

1) Handler Class (Business Logic)

@Component
public class UserHandler {
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
Flux<UserDto> users = Flux.just(
new UserDto(1L, "User1"),
new UserDto(2L, "User2")
);
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(users, UserDto.class);
}

public Mono<ServerResponse> getUserById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return ServerResponse.ok()
.bodyValue(new UserDto(id, "User" + id));
}
}

2) Router Class (Path Mapping)

@Configuration
public class UserRouter {
@Bean
public RouterFunction<ServerResponse> userRoute(UserHandler handler) {
return RouterFunctions.route()
.GET("/api/v2/users", handler::getAllUsers)
.GET("/api/v2/users/{id}", handler::getUserById)
.build();
}
}

3. Reactive Data Handling Example (R2DBC Example)

When integrating with a database, you must use R2DBC, a non-blocking library.

@Repository
public interface UserRepository extends ReactiveSortingRepository<User, Long> {
// Note that return types are Flux or Mono
Flux<User> findByName(String name);
}

@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;

public Mono<User> saveUser(User user) {
return userRepository.save(user);
}

public Flux<User> findUsers(String name) {
return userRepository.findByName(name);
}
}

4. Error Handling

You can handle exceptions gracefully within reactive streams.

public Mono<String> callApiWithRetry() {
return webClient.get()
.uri("/some-error-api")
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.just("Fallback Data")) // Return default value upon error
.retry(3); // Retry 3 times upon error
}
Advertisement