apache/druid · error · IllegalStateException
Metadata should be null because BatchAppenderatorDriver…
Error message
Metadata should be null because BatchAppenderatorDriver never persists it
What it means
BatchAppenderatorDriver never persists run metadata (its underlying appenderator's startJob() must therefore return null metadata). If metadata is non-null, an invariant of the batch driver has been violated — likely the appenderator was configured or subclassed to persist metadata — and this ISE is thrown to surface the inconsistency.
Solutions
- Ensure BatchAppenderatorDriver wraps a BatchAppenderator (created via Appenderators.createBatch), not the streaming Appenderator
- Point the appenderator's base persist directory at a clean location free of prior jobs' persisted state
- If stale state exists, remove the old persist directories for this data source before starting the job
Example fix
// before Appenderator appenderator = Appenderators.createAppendee(...); // streaming appenderator BatchAppenderatorDriver driver = new BatchAppenderatorDriver(appenderator, ...); // after Appenderator appenderator = Appenderators.createBatch(dataSchema, tuningConfig, ...); BatchAppenderatorDriver driver = new BatchAppenderatorDriver(appenderator, ...);
Defensive patterns
Strategy: try-catch
Validate before calling
Object meta = appenderator.startJob();
if (meta != null) {
throw new IllegalStateException("BatchAppenderatorDriver requires an appenderator that persists no metadata");
} Type guard
boolean isBatchAppenderator(Appenderator a) {
return a instanceof BatchAppenderator;
} Try / catch
try {
driver.startJob(lockHelper);
} catch (ISE e) {
if (e.getMessage().contains("Metadata should be null")) {
cleanPersistDirAndRecreateDriver();
}
throw e;
} Prevention
- Always pair BatchAppenderatorDriver with a BatchAppenderator (Appenderators.createBatch)
- Start batch jobs against a clean base persist directory
- Do not reuse an appenderator that previously persisted metadata with the batch driver
When it happens
Trigger: Calling BatchAppenderatorDriver.startJob() when the wrapped appenderator's startJob() returns non-null metadata — e.g. a BatchAppenderator instance that loaded persisted metadata from a prior run, or using a non-batch Appenderator implementation with this driver.
Common situations: Mixing Appenderator implementations: wrapping a regular Appenderator (which persists metadata) in BatchAppenderatorDriver; stale persisted state from a misconfigured base persist directory being picked up at startJob.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Segment[ ] hydrant[ ] already swapped. This cannot happen.
- Sink must not be null for identifier when persisting hydrant
- A batch appenderator was already created for this peon's…
- A realtime appenderator was already created for this peon's…
- announceHistoricalSegments failed with null metadata…
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/f8d917d4d72c634d.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/main/java/org/apache/druid/segment/realtime/appenderator/BatchAppenderatorDriver.java:93
@Nullable
public Object startJob()
{
return startJob(AppenderatorDriverSegmentLockHelper.NOOP);
}
/**
* This method always returns null because batch ingestion doesn't support restoring tasks on failures.
*
* @return always null
*/
@Override
@Nullable
public Object startJob(AppenderatorDriverSegmentLockHelper lockHelper)
{
final Object metadata = appenderator.startJob();
if (metadata != null) {
throw new ISE("Metadata should be null because BatchAppenderatorDriver never persists it");
}
return null;
}
/**
* Add a row. Must not be called concurrently from multiple threads.
*
* @param row the row to add
* @param sequenceName sequenceName for this row's segment
*
* @return {@link AppenderatorDriverAddResult}
*
* @throws IOException if there is an I/O error while allocating or writing to a segment
*/
public AppenderatorDriverAddResult add(
InputRow row,
String sequenceName
) throws IOExceptionView on GitHub (pinned to 9b90983fd2)