Until Successful done right: decision tree, RETRY_EXHAUSTED, and suppressedErrors
Until Successful is not a lucky charm for flaky integrations. It is a synchronous scope that re-runs its processors until they all succeed or maxRetries is exhausted — then raises MULE:RETRY_EXHAUSTED. Teams get burned when they retry business errors, put On Error Continue where Propagate was needed (so the scope “succeeds” and never retries), or handle exhaustion without reading suppressed underlying errors.
What this piece is not: a Medium XML paste, a Studio click-path, or “wrap everything in Until Successful.” It is a decision tree + taxonomy + anti-patterns guide for developers / architects who need connectivity retries without turning validation failures into retry storms.
Below: when to retry, how Try + On Error shapes retry vs skip, what RETRY_EXHAUSTED and suppressedErrors mean, anti-patterns, checklist, FAQ.
What Until Successful actually does
From the Until Successful Scope docs:
- Processors inside run in order, synchronously.
- If any processor fails, Until Successful retries all processors in the scope, including the one that failed, until success or retries are exhausted.
- On final failure: error message like
'until-successful' retries exhausted.— Mule error typeMULE:RETRY_EXHAUSTED. - Attributes:
maxRetries(number or expression);millisBetweenRetries(minimum interval; default 60000 ms; actual interval depends on prior attempt duration, should not exceed twice the configured value). - Variable propagation: every retry starts with the same variables present before the scope. Changes from a failed attempt are not visible on the next attempt. On success, variables and payload propagate to the rest of the flow.
Common doc use cases: outbound endpoints with availability issues, components depending on unreliable resources, re-executing a short chain until it succeeds.
Decision tree: retry or not?
Ask these in order before you drop Until Successful on a call:
1. Is the failure transient (connectivity, short outage, rate-limit-with-backoff)?
NO → do NOT use Until Successful for that error type.
YES → continue.
2. Is the operation idempotent (or safe to re-run the whole scope)?
NO → retrying re-executes ALL processors in the scope — side effects risk.
YES → continue.
3. Can you name the retryable error types (e.g. HTTP:CONNECTIVITY, DB:CONNECTIVITY)?
NO → you will retry BAD_REQUEST / UNAUTHORIZED / business validation by accident.
YES → nest a Try; Propagate only those types.
4. After exhaustion, do you need DLQ / NACK / alert / mapped API error?
YES → flow-level handler on RETRY_EXHAUSTED (+ inspect suppressed cause).
| Situation | Prefer |
|---|---|
Transient *:CONNECTIVITY / short outage |
Until Successful + Try; Propagate retryable types |
HTTP:BAD_REQUEST, auth, schema validation |
No retry — On Error Continue (or Propagate out of US) to a client/DLQ path |
| Exhausted retries | Handle MULE:RETRY_EXHAUSTED; route to DLQ / NACK / alert |
| Critical JVM / overload | Do not treat as “just retry” — see CRITICAL / feature flags |
Taxonomy: Continue vs Propagate inside Until Successful
Until Successful only retries when a processor fails the scope. That is controlled by how you handle errors inside it — almost always via a nested Try.
From On-Error Components / Error Handlers:
| Handler | Effect on owner (Try / flow) | Effect on Until Successful |
|---|---|---|
| On Error Propagate | Owner fails; error re-thrown | Scope sees failure → retry |
| On Error Continue | Owner treated as success | Scope sees success → no retry; flow continues |
Pattern (retry only connectivity):
<until-successful maxRetries="3" millisBetweenRetries="2000">
<try>
<http:request method="GET" config-ref="HTTP_Request_configuration" path="/resource"/>
<error-handler>
<on-error-propagate type="HTTP:CONNECTIVITY"/>
<on-error-continue type="HTTP:BAD_REQUEST, HTTP:UNAUTHORIZED, HTTP:NOT_FOUND">
<!-- map to client/DLQ payload; US will NOT retry -->
</on-error-continue>
</error-handler>
</try>
</until-successful>
Order matters: specific types before ANY. Matching is sequential (On-Error docs).
MULE:RETRY_EXHAUSTED after the last attempt
When the last attempt fails, Until Successful throws MULE:RETRY_EXHAUSTED. Docs show a flow-level handler:
<error-handler>
<on-error-continue type="RETRY_EXHAUSTED">
<logger level="INFO" message="File upload failed"/>
</on-error-continue>
</error-handler>
(Until Successful Scope — FTP example.)
Mule logs each unsuccessful attempt before the final error (Retrying execution of event, attempt N of M).
Mule 3 → 4: deadLetterQueue-ref is gone. Catch RETRY_EXHAUSTED in an error handler and send to your DLQ / endpoint (Migrating the Until Successful).
From Mule Errors: RETRY_EXHAUSTED means retries of an execution block are exhausted (Until Successful or connector retries). Connectors also expose CONNECTIVITY and RETRY_EXHAUSTED in their hierarchies.
Error suppression and suppressedErrors
With the feature flag mule.suppress.mule.exceptions enabled (default), components such as Until Successful and Web Service Consumer report errors in their namespaces (e.g. MULE:RETRY_EXHAUSTED), while the original connector error becomes an underlying / suppressed cause. Docs: Suppressed errors are treated as underlying causes that can also be matched by On Error handlers (Feature Flagging Mechanism; Help: How to disable the error suppression feature in runtime 4.4).
What that means in practice after exhaustion:
- Surface type is often
MULE:RETRY_EXHAUSTED(detailedDescription:'until-successful' retries exhausted). - The last underlying failure (e.g.
HTTP:CONNECTIVITY) appears undersuppressedErrorsin the error object (Help article dump showssuppressedErrors=[ { errorType=HTTP:CONNECTIVITY, ... } ]). - Handlers can match suppressed types as underlying causes — you are not limited to matching only the surface
RETRY_EXHAUSTEDtype when designing routing (feature-flagging docs). - Disabling suppression (
mule.suppress.mule.exceptions=false) is a system / server-level property (Help); do not assume per-app toggle on CloudHub without checking your runtime options.
Honesty note: the public DataWeave selector tables list description, errorType, cause, errorMessage, childErrors (Mule Errors, Predefined Variables) — not a dedicated suppressedErrors row. The Help article’s error dump and feature-flagging docs are the primary Anypoint sources for the suppression model; use type-matching on suppressed causes and/or inspect the error structure in logs/debugger rather than inventing undocumented APIs.
Anti-patterns (and what to do instead)
1. On Error Continue on the only handler inside Until Successful
Symptom: retries never happen; flow continues as if the call succeeded.
Cause: Continue marks the Try/owner successful → Until Successful does not retry.
Fix: Propagate for retryable types; Continue only for non-retryable business errors you intentionally skip.
2. Retrying BAD_REQUEST / validation / auth
Symptom: same 400/401/422 hammered maxRetries times; latency spikes; partner rate limits.
Cause: bare Until Successful around HTTP without Try filtering.
Fix: decision tree step 3 — Propagate only transient types.
3. Non-idempotent processors inside the scope
Symptom: duplicate posts, double charges, duplicate file writes on each attempt.
Cause: docs: on failure, Until Successful retries all processors in the scope, not only the failed one.
Fix: keep the scope minimal (one outbound); move side effects outside or make them idempotent (see OSv2 idempotency article).
4. Handling only RETRY_EXHAUSTED and ignoring the root cause
Symptom: generic “retries exhausted” in ops; no HTTP status / connectivity detail for triage.
Cause: suppression surfaces MULE namespace; underlying sits in suppressed causes.
Fix: log/match suppressed cause; alert on both exhaustion and root type.
5. Expecting Mule 3 failureExpression / deadLetterQueue-ref
Symptom: configs don’t migrate; DLQ never fires.
Fix: Validation processor instead of failureExpression; error handler on RETRY_EXHAUSTED instead of deadLetterQueue-ref (migration guide).
6. Treating CRITICAL / overload as “retryable”
Symptom: retry loops under JVM stress.
Docs adjacency: feature flag mule.untilSuccessful.retryOnCriticalError.disallow — when enabled, events aren’t retried if a MULE:CRITICAL error occurs inside Until Successful (Feature Flagging). Prefer fail-fast + restart/health policy over blind retries.
Checklist
- List retryable vs non-retryable error types for this call.
- Nest Try inside Until Successful; Propagate only retryable types.
- Continue (or exit) for business/validation/auth — no retry.
- Set
maxRetriesandmillisBetweenRetriesconsciously (default interval is 1 minute). - Keep the scope small and idempotent — whole scope re-runs.
- Flow handler for
RETRY_EXHAUSTED→ DLQ / NACK / mapped response / alert. - Account for error suppression: surface may be
MULE:RETRY_EXHAUSTED; inspect suppressed underlying cause. - Remember variable reset between failed attempts.
- MUnit: assert retry counts on Propagate path and zero retries on Continue path.
- Do not use Until Successful as a substitute for a broker (MQ) when you need durable async retry.
FAQ
1. What error does Until Successful throw when retries are exhausted?
MULE:RETRY_EXHAUSTED (message: 'until-successful' retries exhausted.) — Until Successful Scope.
2. Why doesn’t my Until Successful retry?
Usually an On Error Continue (or anything that marks the attempt successful) inside the scope. Propagate for errors you want retried.
3. Does a retry re-run only the failed processor?
No — docs: it retries all processors within the scope, including the one that failed.
4. What are suppressed errors?
With mule.suppress.mule.exceptions (default), Until Successful reports in the MULE namespace; underlying connector errors are suppressed causes that can still be matched by On Error handlers (Feature Flagging; Help).
5. How do I replace Mule 3 deadLetterQueue-ref?
Catch RETRY_EXHAUSTED in an error handler and route to your DLQ (migration).
6. Default millisBetweenRetries?
60000 (one minute) if unset (Until Successful Scope).
7. Should I retry HTTP 400?
Almost never — treat as non-retryable via On Error Continue (or fail without US).
8. Are variables kept across failed attempts?
No — each attempt starts from pre-scope variables; failed-attempt changes are discarded (Until Successful Scope).
9. Continue vs Propagate in one sentence?
Continue = owner succeeds (no US retry). Propagate = owner fails (US retries).
Soft CTA
Designing retry vs fail-fast for System API calls (connectivity vs validation) and want a review of Until Successful nesting, RETRY_EXHAUSTED handlers, and suppression behavior before production grows a retry storm? Solita is a Nordic MuleSoft partner with delivery from Poland (EU-shoring) — we help shape error taxonomy and DLQ paths. No “#1” claims and no marketing checklists.
Sources
Documentation
- Until Successful Scope
- On-Error Components
- Error Handlers
- Try Scope
- Mule Errors
- Migrating the Until Successful
- Feature Flagging Mechanism (
mule.suppress.mule.exceptions,mule.untilSuccessful.retryOnCriticalError.disallow) - Predefined Variables
Help
- How to disable the error suppression feature in runtime 4.4 (
suppressedErrorsdump withMULE:RETRY_EXHAUSTED)
YouTube (oEmbed-verified 2026-09-17)
- https://www.youtube.com/watch?v=DqW-SWQsf4k — Until Successful Scope in Mule Application (Sanjeev Tripathi)
- https://www.youtube.com/watch?v=gKjEjAD851M — HTTP Request within Until Successful & Try Scope (MuleSoft-TechZone)
- https://www.youtube.com/watch?v=xfAyWA9Ijpo — Until Successful / Propagate System API Errors (MuleSoft-TechZone)
- https://www.youtube.com/watch?v=a86tdUBu9lQ — Mule 4 Error Handling — 3 simple rules (MuleSoft-TechZone)