Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fuse

Fuse

Build Maven Central License

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.


Why this and not Resilience4j?

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.


Installation

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")

Quick start

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());

How it works

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.

Configuration

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

Filtering exceptions

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.


Listeners

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.


Thread safety

The implementation uses lock-free JDK primitives:

  • AtomicReference for state transitions (CAS)
  • AtomicInteger for counters
  • CopyOnWriteArrayList for listeners

No synchronized anywhere. You can share the same instance across all your application threads without worrying about it.


Full API

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.


Building from source

git clone https://github.com/frodyGr/Fuse.git
cd Fuse
mvn clean verify

You'll need JDK 17+ and Maven 3.9+. That's it.


Contributing

PRs are welcome. The usual flow:

  1. Fork → branch → commit → push → PR
  2. Make sure mvn verify passes
  3. 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.


Roadmap

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.


License

Apache 2.0. Do whatever you want with it.

About

No description, website, or topics provided.

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages