How to use WebClient to execute synchronous request?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Spring WebClient is designed for reactive, non-blocking HTTP calls, but many applications still need a synchronous result at a boundary such as a service method, batch job, or command-line tool. The usual pattern is to build a reactive pipeline and call block() only at the edge. This article shows how to do that safely, configure timeouts, and avoid the most common mistakes.
What "Synchronous" Means with WebClient
WebClient itself is still a reactive client. A call becomes synchronous only when you wait for the result, typically by calling block() on a Mono.
That distinction matters because:
- request building remains lazy until subscription
- filters and error handlers still behave reactively
- blocking on the wrong thread can hurt scalability
If your application is already reactive end to end, prefer returning Mono or Flux instead of blocking. If you are integrating with imperative code, blocking at the outer boundary is reasonable.
Create a Basic WebClient
Start with a reusable client configured with a base URL and default headers.
This client can be injected into services and reused across requests.
Execute a Synchronous GET
For a simple GET, call retrieve(), convert the response body, and then block().
block() waits for the remote response and returns the deserialized object, so from the caller's perspective this is synchronous.
Send a Synchronous POST
Posting JSON works the same way, except you pass a request body.
This is a practical pattern for internal service clients where the surrounding code is imperative.
Handle HTTP Errors Explicitly
retrieve() throws a WebClientResponseException for 4xx and 5xx by default. For better control, map status codes to application exceptions.
Without explicit handling, debugging failed requests becomes slower because you lose business-specific context.
Add Timeouts for Blocking Calls
If you choose to block, you should also bound how long you are willing to wait.
For production systems, combine this with underlying HTTP client timeouts so connection setup, read timeout, and total wait time are all controlled.
Service Integration Pattern
The safest rule is: keep reactive details inside the client, and expose an imperative method only if the rest of the application is imperative.
This keeps the blocking point centralized, which is easier to test and reason about.
When Not to Block
Do not call block() inside a fully reactive request flow such as a Spring WebFlux controller. That defeats the non-blocking model and can cause thread starvation under load.
In those cases, return a Mono of Todo instead:
Use the synchronous wrapper only when you genuinely need an immediate result.
Common Pitfalls
- Calling
block()deep inside shared library code instead of at the application boundary. - Forgetting to configure timeouts, which can leave threads waiting indefinitely.
- Using
block()inside reactive controllers or reactive service chains. - Ignoring status code handling and relying only on generic exceptions.
- Creating a new
WebClientfor every request instead of reusing a configured instance.
Summary
- '
WebClientcan be used synchronously by callingblock()on aMono.' - Keep blocking at the edge of an imperative workflow, not inside reactive chains.
- Reuse a configured
WebClientinstance for cleaner and more efficient code. - Add explicit error handling and timeout control for production safety.
- If your application is reactive end to end, return
MonoorFluxinstead of blocking.

