apache/druid · error · SegmentValidationException
Validation failure on row
Error message
Validation failure on row %d: [%s] vs [%s]
What it means
When validateRowValues fails for a row pair inside validateTwoSegments, the caught SegmentValidationException is rethrown wrapped with row context: 'Validation failure on row %d: [%s] vs [%s]'. The original cause (e.g. dimension value or metric mismatch) is preserved as the exception cause.
Solutions
- Read the cause chain (getCause()) for the underlying validateRowValues error — the wrapper only locates the row.
- Compare the printed row pointers' values to find which column differs.
- Fix the converter/ingestion step that mutated the values and rebuild the segment.
- If the diff is benign (e.g. formatting), update the validation tolerance or the reference segment.
Example fix
// before: catching and losing context
try { io.validateTwoSegments(a, b); } catch (SegmentValidationException e) { log.error(e.getMessage()); }
// after: surface the root cause and row
try { io.validateTwoSegments(a, b); } catch (SegmentValidationException e) {
log.error(e, "Validation failed: %s | cause: %s", e.getMessage(), e.getCause());
} Defensive patterns
Strategy: try-catch
Try / catch
try {
io.validateTwoSegments(adapter1, adapter2);
} catch (SegmentValidationException e) {
Throwable cause = e.getCause();
log.error("Row-level validation failed: %s | root cause: %s", e.getMessage(),
cause == null ? "n/a" : cause.getMessage());
throw e;
} Prevention
- Always inspect getCause() — this message wraps the actual value mismatch.
- Log both row pointers from the message to locate the differing column.
- Validate value-preserving behavior of converters with unit tests on sample rows.
- Watch for lossy conversions (float precision, encoding) when rewriting segments.
When it happens
Trigger: Any validateRowValues failure (dimension values, metric values, timestamps differing between adapters) for the row at position %d; the wrapper identifies which row and shows both row pointers.
Common situations: Data rewritten by a converter that altered values (e.g. lossy numeric conversion, changed string encoding); rollup/dedup differences between builds; null-handling changes across Druid versions in validated segments.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Dim [ ] values not equal. Expected found
- Dimension names differ. Expected
- Encountered error while reading the output of stage
- got an exception while loading key
- Metric names differ. Expected
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/a22546ab45eaac88.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/segment/IndexIO.java:194
final RowIterator it1 = adapter1.getRows();
final RowIterator it2 = adapter2.getRows()
) {
long row = 0L;
while (it1.moveToNext()) {
if (!it2.moveToNext()) {
throw new SegmentValidationException("Unexpected end of second adapter");
}
final RowPointer rp1 = it1.getPointer();
final RowPointer rp2 = it2.getPointer();
++row;
if (rp1.getRowNum() != rp2.getRowNum()) {
throw new SegmentValidationException("Row number mismatch: [%d] vs [%d]", rp1.getRowNum(), rp2.getRowNum());
}
try {
validateRowValues(rp1, adapter1, rp2, adapter2);
}
catch (SegmentValidationException ex) {
throw new SegmentValidationException(ex, "Validation failure on row %d: [%s] vs [%s]", row, rp1, rp2);
}
}
if (it2.moveToNext()) {
throw new SegmentValidationException("Unexpected end of first adapter");
}
if (row != adapter1.getNumRows()) {
throw new SegmentValidationException(
"Actual Row count mismatch. Expected [%d] found [%d]",
row,
adapter1.getNumRows()
);
}
}
}
public QueryableIndex loadIndex(File inDir) throws IOException
{
return loadIndex(inDir, false, SegmentLazyLoadFailCallback.NOOP);View on GitHub (pinned to 9b90983fd2)