LMAX-Exchange/disruptor · error · UnsupportedOperationException

Rewindable Exception thrown from a non-rewindable event hand

Error message

Rewindable Exception thrown from a non-rewindable event handler

What it means

Thrown by the NoRewindHandler inside BatchEventProcessor when a RewindableException propagates from an event handler that does not implement RewindableEventHandler. Rewind (replay a batch) is only possible for handlers that explicitly opt in, because only they know how to make their processing idempotent; for plain EventHandler/BatchEventHandler the processor cannot safely retry.

Source

Thrown at src/main/java/com/lmax/disruptor/BatchEventProcessor.java:310

        {
            if (batchRewindStrategy.handleRewindException(e, ++retriesAttempted) == REWIND)
            {
                return startOfBatchSequence;
            }
            else
            {
                retriesAttempted = 0;
                throw e;
            }
        }
    }

    private static class NoRewindHandler implements RewindHandler
    {
        @Override
        public long attemptRewindGetNextSequence(final RewindableException e, final long startOfBatchSequence)
        {
            throw new UnsupportedOperationException("Rewindable Exception thrown from a non-rewindable event handler", e);
        }
    }
}

View on GitHub (pinned to c871ca4982)

Solutions

  1. Make the handler implement RewindableEventHandler (onEvent plus onBatchStart/rewind support) so rewind is legitimate.
  2. If rewind is not needed, throw a different exception type and handle it via an ExceptionHandler instead of RewindableException.
  3. Audit the handler chain: the exception may originate in a delegate/inner handler that still throws RewindableException.

Example fix

// before
class MyHandler implements EventHandler<Event> {
    public void onEvent(Event e, long seq, boolean endOfBatch) {
        if (notReady()) throw new RewindableException(); // -> UnsupportedOperationException
    }
}

// after
class MyHandler implements RewindableEventHandler<Event> {
    public void onEvent(Event e, long seq, boolean endOfBatch) {
        if (notReady()) throw new RewindableException(); // now rewound via BatchRewindStrategy
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(handler instanceof RewindableEventHandler) && exception instanceof RewindableException) {
    throw new IllegalStateException("handler cannot rewind; fix wiring", exception);
}

Type guard

static boolean canRewind(EventHandler<?> h) {
    return h instanceof RewindableEventHandler;
}

Try / catch

try { processor.run(); }
catch (UnsupportedOperationException e) {
    if (e.getMessage() != null && e.getMessage().contains("non-rewindable")) {
        // handler must implement RewindableEventHandler, or stop throwing RewindableException
    } else throw e;
}

Prevention

When it happens

Trigger: Registering a plain EventHandler (or BatchEventHandler) that throws a RewindableException (or an exception wrapping one) — the processor consults its rewind handler, gets NoRewindHandler because instanceof RewindableEventHandler was false, and this UnsupportedOperationException escapes.

Common situations: Developer copies RewindableException usage from another codebase where the handler implemented RewindableEventHandler; refactoring a rewindable handler into a plain one while keeping the exception; a downstream library throwing RewindableException into a non-rewindable handler.

Related errors


AI-assisted analysis of LMAX-Exchange/disruptor@c871ca4982 (2026-08-14). Data as JSON: /api/errors/a2e60068e1c9ab72. Report an issue: GitHub.