apache/seatunnel · error · IllegalArgumentException

Already an MDCRunnable

Error message

Already an MDCRunnable

What it means

MDCTracer.tracing(context, Runnable) rejects a delegate that is already an MDCRunnable. Wrapping twice would apply MDC context twice and restore it incorrectly, so the library fails fast with IllegalArgumentException.

Source

Thrown at seatunnel-api/src/main/java/org/apache/seatunnel/api/tracing/MDCTracer.java:66

 *    logger.info("Task is running");
 *    return null;
 *    }));
 *
 * }</pre>
 */
public class MDCTracer {

    public static MDCRunnable tracing(Runnable delegate) {
        return tracing(MDCContext.current(), delegate);
    }

    public static MDCRunnable tracing(Long jobId, Runnable delegate) {
        return tracing(MDCContext.of(jobId), delegate);
    }

    public static MDCRunnable tracing(MDCContext context, Runnable delegate) {
        if (delegate instanceof MDCRunnable) {
            throw new IllegalArgumentException("Already an MDCRunnable");
        }
        return new MDCRunnable(context, delegate);
    }

    public static <V> MDCCallable<V> tracing(Callable<V> delegate) {
        return tracing(MDCContext.current(), delegate);
    }

    public static <V> MDCCallable<V> tracing(Long jobId, Callable<V> delegate) {
        return tracing(MDCContext.of(jobId), delegate);
    }

    public static <V> MDCCallable<V> tracing(MDCContext context, Callable<V> delegate) {
        if (delegate instanceof MDCCallable) {
            throw new IllegalArgumentException("Already an MDCCallable");
        }
        return new MDCCallable<>(context, delegate);
    }

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Pass the raw Runnable to tracing() only once
  2. If the delegate may already be wrapped, check `if (delegate instanceof MDCRunnable)` and use it directly instead of wrapping again
  3. Remove the inner tracing() call at one of the layers

Example fix

// before
MDCRunnable r = MDCTracer.tracing(jobId, MDCTracer.tracing(jobId, task));
// after
MDCRunnable r = MDCTracer.tracing(jobId, task);
Defensive patterns

Strategy: type-guard

Validate before calling

MDCRunnable safeWrap(Long jobId, Runnable r) {
    return r instanceof MDCRunnable ? (MDCRunnable) r : MDCTracer.tracing(jobId, r);
}

Type guard

boolean isWrapped(Runnable r) {
    return r instanceof MDCRunnable;
}

Try / catch

try {
    return MDCTracer.tracing(jobId, runnable);
} catch (IllegalArgumentException e) {
    return (MDCRunnable) runnable;
}

Prevention

When it happens

Trigger: Calling MDCTracer.tracing(jobId, runnable) or tracing(context, runnable) where runnable is already the result of a previous tracing() call, e.g. double-wrapping in shared submission code or a scheduler that wraps tasks twice.

Common situations: A task wrapper layer and an executor layer both call MDCTracer.tracing on the same Runnable; refactors that moved tracing to an outer layer without removing the inner call.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/336404aba610eeb9. Report an issue: GitHub.