Yet another Circuit Breaker? Yes, but this one doesn't drag half the internet in transitive dependencies.
Fuse is a Java 17+ library that implements the Circuit Breaker pattern for microservices. Zero runtime dependencies — everything it uses comes straight from the JDK (java.util.concurrent). Drop the JAR in and you're done.
Fair question. If you need rate limiting, bulkhead, retry decorators and the whole toolkit, Resilience4j is your thing. I'm not trying to compete with that.
But if all you need is a circuit breaker that works, is thread-safe, and doesn't shove 15 transitive JARs into your classpath... then this might be what you're looking for.
| fuse | resilience4j-circuitbreaker | |
|---|---|---|
| Runtime dependencies | 0 | ~5 transitive |
| JAR size | < 20 KB | ~200 KB (module only) |
| Learning curve | 5 minutes | Extensive docs |
Nothing against Resilience4j — it's just a matter of scope.
Maven
<dependency>
<groupId>io.github.frodygr</groupId>
<artifactId>fuse</artifactId>
<version>1.0.0</version>
</dependency>Gradle
implementation("io.github.frodygr:fuse:1.0.0")import io.github.frodygr.circuitbreaker.*;
import java.time.Duration;
var config = CircuitBreakerConfig.builder()
.failureThreshold(5) // 5 consecutive failures → open
.successThreshold(3) // 3 successes in half-open → close
.timeout(Duration.ofSeconds(30)) // stay open 30s before probing
.build();
var cb = new DefaultCircuitBreaker("payment-service", config);
try {
String response = cb.execute(() -> httpClient.send(request));
} catch (CircuitBreakerOpenException e) {
// Circuit is open, fall back
return cachedResponse();
}Works with Runnable too if you don't need a return value:
cb.execute(() -> metricsCollector.flush());The pattern follows a pretty straightforward state machine:
CLOSED ──── failures ≥ threshold ────► OPEN
│
timeout expires
│
▼
HALF_OPEN
/ \
success(es) failure
↓ ↓
CLOSED OPEN
- CLOSED — Business as usual. Requests go through. Failures are counted.
- OPEN — Something's wrong. Requests are rejected immediately with a
CircuitBreakerOpenException. No waiting, no caller-side timeouts. - HALF_OPEN — Timeout has passed. A few probe requests are let through. If they work, circuit closes. If they fail, back to open.
Everything goes through CircuitBreakerConfig.builder():
| Parameter | Default | What it does |
|---|---|---|
failureThreshold |
5 | Consecutive failures to open the circuit |
successThreshold |
3 | Consecutive successes in HALF_OPEN to close it |
timeout |
60s | How long the circuit stays open before moving to HALF_OPEN |
recordException |
all | Predicate to filter which exceptions count as failures |
Not everything should count as a failure. For instance, a 400 Bad Request is the caller's fault, not the downstream service's:
var config = CircuitBreakerConfig.builder()
.recordException(ex -> ex instanceof IOException)
.build();With this, only IOException (and subclasses) bump the failure counter. Everything else passes through without touching the circuit state.
If you want to know about state transitions (for logging, metrics, alerting...):
cb.onStateChange((name, from, to) ->
log.warn("[{}] circuit breaker: {} → {}", name, from, to));You can register multiple listeners. If a listener throws, it gets swallowed internally — it won't break the circuit breaker's flow.
The implementation uses lock-free JDK primitives:
AtomicReferencefor state transitions (CAS)AtomicIntegerfor countersCopyOnWriteArrayListfor listeners
No synchronized anywhere. You can share the same instance across all your application threads without worrying about it.
public interface CircuitBreaker {
<T> T execute(Supplier<T> supplier); // most common
<T> T executeChecked(Callable<T> callable) // checked exceptions
throws Exception;
void execute(Runnable runnable); // fire-and-forget
CircuitBreakerState getState();
String getName();
void reset();
void onStateChange(EventListener listener);
}reset() forces the circuit back to CLOSED and clears all counters. Handy for tests or if you want to expose an admin endpoint.
git clone https://github.com/frodyGr/Fuse.git
cd Fuse
mvn clean verifyYou'll need JDK 17+ and Maven 3.9+. That's it.
PRs are welcome. The usual flow:
- Fork → branch → commit → push → PR
- Make sure
mvn verifypasses - If you're adding functionality, add tests
No need to open an issue before sending a PR for small changes. For bigger stuff, it's a good idea to discuss it first.
Things I'd like to add when I get the time:
- Sliding window (count-based and time-based) instead of a simple counter
- Exportable metrics (probably Micrometer-compatible)
- Retry as a companion pattern
- Bulkhead pattern
- Rate limiter
No promises on dates.
Apache 2.0. Do whatever you want with it.
