Pattern · seen in 2 breakdowns across 2 companies

Retryable Error Classification

Sort every error into safe to retry or not, put that label in the response, and clients will retry the temporary faults and give up fast on the ones that would just fail again.

The mechanism

At its core: each error is either temporary (a retry may work) or permanent (a retry never will), and labeling one as the other has a cost both ways - give up too early, or retry something that keeps failing.

LIVE ARTIFACTTWO WAYS TO BE WRONGOPEN FULL SCREEN ↗
THE IDEAEvery failure is either temporary or permanent. A temporary failure - a network blip, a busy server - may clear on the next try. A permanent failure - bad input, a state that will not change - will fail the same way every time. If you retry a permanent failure you waste work and can repeat a side effect, like charging a card twice; if you give up on a temporary one you fail a request that would have recovered. The pattern labels each error safe to retry or not, puts the label in the response, and defaults to do not retry when it cannot tell and the action cannot be undone.
WHAT TO TRYPick an error and choose whether to retry it - watch it land in one of four cells, two good and two costly. Then try the unknown error on a payment and see why the safe default is do not retry.

Label an error right and a retry helps; label it wrong and you either give up too soon or charge the card twice.

Definition

When a request fails, something has to decide whether trying again is worth it. Some failures are temporary: a network blip, a server that was busy for a moment. Trying again will probably work. Other failures are permanent: bad input, or an invalid action. Trying again will fail the exact same way, every time. So the two kinds need opposite handling: retry the temporary ones, stop on the permanent ones. A single rule for every error gets one group wrong.

Retryable error classification sorts every failure into one of two labels:

  • safe to retry - the failure looks temporary, so trying again may work
  • do not retry - the failure is permanent, so trying again will fail the same way

The label goes into the response the server sends back, so clients and frameworks do not have to guess: they only retry the failures marked safe to retry. Retries go out under the same idempotency key, a marker that lets the server tell a retry apart from a brand-new request, so a safe retry does not do the work twice. When the label is missing and the action cannot be undone, treat it as do not retry - the careful default.

Getting the label wrong costs something in both directions. Mark a temporary failure as permanent and you fail a request that a retry would have fixed. Because the label says permanent, nothing ever tries again. Mark a permanent failure as temporary and you retry something that cannot succeed, which wastes work and can repeat a side effect that already happened, like a double charge that a person then has to clean up.

This is a different question from how much to retry. A retry budget caps how much retrying a system can afford; classification decides whether a given failure should be retried at all. You want both: the budget keeps retries from piling up, and the classification keeps them pointed only at failures that a retry can actually fix.

BEFORE A RETRY GOES OUT
Three gates before a retry goes out: is it retryable, is there budget, and how long to wait.
Classification is the first gate a retry passes: whether this failure may be retried at all, before a retry budget limits how many and backoff and jitter decide how long to wait.

When it applies

01A framework retries for you, automatically. When retrying is built into shared code rather than decided by hand at each call, that code needs a rule for which errors are safe to retry.
02The API returns a mix of error kinds. Some are server or network problems that may pass (the 5xx kind), and some are bad-request or bad-state errors that will not (the 4xx kind), so one retry rule for all of them is wrong.
03The action cannot be undone. For things like charging a card or shipping an order, wrongly retrying is worse than wrongly giving up, so the safe default matters most here.

Tradeoffs

Every error needs a deliberate label. As the code grows, each new failure path has to be classified on purpose, and one badly labeled error can undo the whole benefit.
Some errors are genuinely ambiguous. The same empty result can come from a network blip or from bad input, and the layer doing the labeling may not have enough context to tell which.
The safe default has its own cost. Defaulting unknown errors to do not retry protects against double actions, but it quietly turns some temporary faults into hard failures that a person has to go in and fix by hand.

The same move, 2 ways

Every row is a production system that bet on this pattern — the note says how, in that system's own terms.

Amazon (AWS)
Amazon Builders' Library
2021
AWS turns the whole client contract into a single line to classify errors by: any error that isn't a validation error can be retried until it succeeds. That one rule is what the SDK's default retry policy runs on - it retries everything on the non-validation side and stops on the validation side. And the validation side does real work here, not just rejection: when a reused token arrives with different parameters, the service returns a validation error precisely so the retry won't blindly repeat against a changed request. Airbnb drew the same line inside an internal framework with per-exception judgments; AWS draws it at the public API boundary itself, where the SDK can act on it for every caller. Read the breakdown →
Airbnb
Airbnb Engineering
2019
Every failure is sorted into two buckets: retryable ones, presumed temporary and safe to try again under the same key, and non-retryable ones, treated as permanent so the recorded failure is simply replayed. The default leans to non-retryable, the safe direction for money. This is what turns 'a retry is safe' from a vague hope into a concrete, per-error rule the framework can act on. Read the breakdown →

Often used together

Patterns sharing breakdowns with this one — derived from co-occurrence, threshold ≥2 shared.

Problems this pattern answers

The walls where its breakdowns live — each opens the cross-company comparison.