# Gof Chain Of Responsibility

> Chain of Responsibility in modern Java, and the pipeline it is usually confused with: the classical first-accepting form versus middleware where stages may all process and forward conditionally. Covers choosing between them, the unhandled-request policy that silent chains get wrong, ordering discipline when handlers are contributed independently, error propagation and partial state when a stage throws mid-chain, and why servlet filters and interceptor chains are this pattern already implemented. Use when a request must be offered to several possible handlers, when @Order values are tuned to make a chain work, when a request falls off the end of a chain and nothing happens, or when a chain is proposed for three fixed cases. Does not cover the security framework's own filter configuration, the retry and timeout policies applied around a call (gof-decorator, circuit-breakers), or message processing across services (streaming-pipeline-topologies).

- Skill: `robsonkades/gof-chain-of-responsibility` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add robsonkades/gof-chain-of-responsibility`
- Raw SKILL.md: https://api.skillmd.com/api/skills/robsonkades/gof-chain-of-responsibility/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: robsonkades (https://skillmd.com/u/robsonkades)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/robsonkades/gof-chain-of-responsibility

---


# Chain of Responsibility

## Purpose

Let a request be offered to a sequence of candidate handlers without the sender knowing which one
will deal with it. The sender depends on the chain, not on the handlers, so handlers can be
added, removed and reordered without touching it.

Two shapes travel under this name and behave differently:

```text
Classical CoR      each handler decides whether to handle or pass. First-match-wins
                   is common, but a contract may allow handling and continuation.
                   Fallthrough to the end is a defined outcome.

Pipeline /         every stage processes and passes on: filters,
middleware         interceptors, Netty handlers, Spring Security's chain.
                   All stages run unless one short-circuits deliberately.
```

Most modern uses are the second. Deciding which you are building is the first design step,
because the unhandled case, the ordering rules and the error semantics all differ.
The partial Java examples use Java 17 unless labelled otherwise. Pattern switches over sealed
types are final in Java 21; on Java 17 use an enum switch or explicit dispatch without enabling
preview merely for this pattern. Inspect actual framework versions and target toolchains.

## When it is the answer

```text
The set of handlers is open — new ones arrive from other modules,
plugins or configuration
        → Chain. A switch would have to be edited by every contributor.

Order is meaningful and must be configurable
        → Chain, with the order stated explicitly rather than implied.

A request may be handled at different levels of specificity
(tenant rule → product rule → default)
        → Classical CoR, with the default as the last link.

Cross-cutting work must wrap request handling
        → Pipeline — and prefer the framework's, which already has
          ordering, error translation and observability.
```

## When it is not

- **Three fixed cases you own.** A `switch` over a sealed type is shorter, exhaustive and
  readable; a chain hides the whole decision behind wiring (`java-composition-over-inheritance`).
- **Every handler must run and none may decline.** This is the pipeline/middleware variant of CoR;
  name its no-short-circuit contract so a handler cannot silently skip required stages.
- **The framework already provides it.** A hand-rolled chain beside servlet filters or
  `HandlerInterceptor` duplicates ordering and is invisible to the framework's metrics and
  tracing.
- **Handlers need to know about each other.** Then the chain is a workflow with implicit
  coupling; make the sequence explicit or use a mediator (`gof-mediator`).
- **The chain spans services.** A sequence of network hops is a workflow or a saga with partial
  failure at every step, not this pattern (`distributed-transactions-and-sagas`).

## Decision rules

```text
IF nothing handles the request
THEN define whether this is a no-op/not-applicable result, a terminal default, or an
     error. Silent fallthrough is correct only when the API makes that outcome visible.

IF handler order is expressed as @Order(100), @Order(200)
THEN the ordering rationale exists only in someone's head. Name the
     positions (an enum, an explicit list at the composition root) so
     the reason survives.

IF a handler both handles and forwards, in a chain designed for
"first match wins"
THEN the two shapes have been mixed and downstream handlers now see a
     request that was already handled.

IF a stage mutates shared state and a later stage throws
THEN the request leaves partial effects behind. Either make stages
     pure over a context object and apply effects at the end, or define
     an applicable transaction/compensation boundary. Deferring effects alone does not make
     their final application atomic or idempotent under retry.

IF handlers hold per-request state in fields
THEN a shared chain is not thread-safe. State belongs in the context
     object passed along the chain, not in the handler.

IF the chain is assembled at every request
THEN determine whether tenant, capability or request data genuinely changes membership.
     Otherwise precompute immutable chains; when it does, cache bounded variants or
     measure per-call assembly rather than assuming it is free.

IF a chain is used for validation and stops at the first failure
THEN callers get one problem at a time. Decide deliberately: fail fast,
     or collect every violation (java-exception-design).
```

## Modern Java expression

```text
Classical                            Modern
───────────────────────────────────  ───────────────────────────────────
abstract Handler with a successor    a List<Handler> iterated by the
field and setNext()                  chain owner — order is data, not a
                                     linked structure nobody can see

handler.handle(request) returns      Optional<Result> handle(Request),
void and mutates                     with the chain taking the first
                                     non-empty

pipeline via successor calls         Function composition, or the
                                     framework's filter chain

per-request state in ThreadLocal     a context record passed along, or
                                     ScopedValue (scoped-values)
```

A `List<Handler>` plus `stream().flatMap(h -> h.handle(req).stream()).findFirst()` expresses
sequential classical CoR with the order visible at the composition root and no successor wiring.
Do not use a parallel stream when later handlers must never execute after the first decision;
ordered result selection does not guarantee exclusive invocation. Keep
the linked form only when a handler must decide _how_ to invoke the rest — wrapping it in a
try/finally, running it on another thread, or skipping it — which is the pipeline shape.

## Cross-cutting checks

- **Concurrency.** A shared chain may be used concurrently, so handlers and their dependencies
  need an explicit thread-safety contract; per-request state should travel in the request or a context
  object. `ThreadLocal` can leak across pooled threads if not cleared and does not automatically
  follow arbitrary executor handoffs; `ScopedValue` (final in Java 25) is suited to immutable
  dynamically scoped context, not a general mutable replacement
  (`scoped-values`, `thread-sizing-and-virtual-threads`).
- **Distribution.** Chains that process messages must define what a mid-chain failure means for
  acknowledgement: a stage that throws after a side effect has been applied, in an at-least-once
  system, will re-run the earlier stages on redelivery. Make stages idempotent or apply effects
  through an idempotent/transactional commit boundary even if deferred until the end
  (`idempotency`, `delivery-semantics`, `poison-messages-and-dlq`). Cancellation
  must also propagate — a chain that ignores an expired deadline keeps working for a caller that
  has gone (`cancellation-and-interruption`).
- **Performance.** Cost depends on traversal, dispatch and context design; a context may already
  exist and need not be allocated by each link. Important patterns are a chain that computes an expensive value for every
  handler to inspect rather than lazily, and a chain long enough that the call site becomes
  megamorphic in a hot path (`jit-inlining-and-escape-analysis`).
- **Testing.** Three distinct tests. Each handler alone, with a trivial context. The chain's
  order, asserting that a request matching two handlers reaches the intended one. And the
  unhandled case, asserting the defined behaviour — the test most often missing, and the one that
  catches a silent drop.

## Review checklist

- [ ] The shape is stated: first-match-wins, or every-stage-runs
- [ ] The unhandled outcome is defined and covered by a test
- [ ] Order is expressed as an explicit list or named positions, not bare numbers
- [ ] Handlers hold no per-request state in fields
- [ ] Partial effects, final commit failure and redelivery have explicit transaction/idempotency/recovery contracts
- [ ] Chain assembly lifetime matches actual variability and is measured/cached when request-specific
- [ ] Deadlines and cancellation propagate through the chain
- [ ] The framework's own chain was considered for cross-cutting concerns
- [ ] A closed set was compared with an exhaustive switch; chain ordering/composition still has a stated benefit

## References

- [Chain against pipeline](references/chain-vs-pipeline.md) — the two shapes with their differing
  contracts, ordering discipline and how to make it survive contributors, unhandled-request
  policies, error propagation and partial state, and the framework equivalents worth using
  instead. Read before assembling a chain.
- [Worked example](references/worked-example.md) — a payment-authorisation rule chain replacing a
  branching method: the first-match version, the ordering made explicit, the terminal default,
  what happened when a stage acquired a side effect, and the three tests. Read when implementing.

