Pro Tips — BlockHound Blocking Detection and Resilience4j Circuit Breakers
1. BlockHound — Blocking Code Intrusion Detector for Reactive Pipelines
If blocking code accidentally sneaks into a WebFlux pipeline (e.g., JDBC calls, Thread.sleep(), File.read()), it occupies event loop threads and can bring the entire server to a standstill. BlockHound immediately detects these dangerous blocking calls as runtime exceptions during testing and development.
testImplementation 'io.projectreactor.tools:blockhound:1.0.8.RELEASE'
@SpringBootTest
public class BlockHoundTest {
@BeforeAll
static void setup() {
BlockHound.install(); // Install before JUnit runs
}
@Test
void blocking_code_should_fail_test() {
StepVerifier.create(
Mono.delay(Duration.ofMillis(1))
.doOnNext(it -> {
try {
Thread.sleep(10); // ← BlockHound detects this blocking call and throws!
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
})
).verifyError(BlockingOperationError.class); // Expects the error (intentional test)
}
}
2. Resilience4j Circuit Breaker — Isolating External API Failures
Continuously calling a downed external API exhausts your thread pool and causes cascading failures. A Circuit Breaker blocks requests outright when the failure rate exceeds a threshold, responding with fast-fail rather than letting the failure propagate.
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
resilience4j.circuitbreaker:
instances:
externalApiCb:
failure-rate-threshold: 50 # Switch to OPEN if >50% requests fail
wait-duration-in-open-state: 10s # After 10s, switch to HALF-OPEN to retry
sliding-window-size: 10 # Evaluate based on the last 10 requests
@Service
@RequiredArgsConstructor
public class ExternalPaymentService {
private final WebClient webClient;
@CircuitBreaker(name = "externalApiCb", fallbackMethod = "paymentFallback")
public Mono<PaymentResult> requestPayment(PaymentRequest request) {
return webClient.post()
.uri("/external/pay")
.bodyValue(request)
.retrieve()
.bodyToMono(PaymentResult.class);
}
// Executed when the circuit is open or an exception occurs
public Mono<PaymentResult> paymentFallback(PaymentRequest request, Throwable t) {
log.error("Payment server failure — activating fallback: {}", t.getMessage());
return Mono.just(PaymentResult.failure("PAYMENT_SERVER_ERROR"));
}
}