apache/druid · error · IllegalStateException (ISE)

Unable to compare timestamp for rows without a time column

Error message

Unable to compare timestamp for rows without a time column

What it means

getFirstEventTimestamp reads the __time column of a ScanResultValue's first event (list format, where events are Maps). If the event map has no __time entry, rows cannot be time-compared for stable ordering, so Druid throws an IllegalStateException.

Solutions

  1. Include __time in the query's columns list (in SQL, select __time or avoid excluding it) when using time-ordering.
  2. Remove the time-ordering if the time column is intentionally not part of results.
  3. If building ScanResultValues manually, always populate the __time key in each event map.

Example fix

// before
new ScanQueryBuilder().columns(Arrays.asList("col1")).order(Order.DESCENDING).build();
// after
new ScanQueryBuilder().columns(Arrays.asList(ColumnHolder.TIME_COLUMN_NAME, "col1")).order(Order.DESCENDING).build();
Defensive patterns

Strategy: validation

Validate before calling

if (query.getColumns() != null && !query.getColumns().contains(ColumnHolder.TIME_COLUMN_NAME) && query.getOrdering() != null) {
  throw new IllegalArgumentException("time-ordered scans must include __time");
}

Type guard

boolean firstEventHasTime(ScanResultValue sv) {
  Object ev = ((List<?>) sv.getEvents()).get(0);
  return ev instanceof Map && ((Map<?, ?>) ev).containsKey("__time");
}

Try / catch

try {
  long ts = sv.getFirstEventTimestamp(format);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("without a time column")) { /* add __time to columns and re-query */ }
  else throw e;
}

Prevention

When it happens

Trigger: stableLimitingSort comparing ScanResultValues whose first event (a Map) lacks ColumnHolder.TIME_COLUMN_NAME — e.g. scan queries that excluded __time from the columns list but requested descending time-ordering.

Common situations: SQL 'SELECT col1, col2 FROM t ORDER BY __time DESC' plans where __time is not projected; custom scans with explicit column lists omitting __time while ordering is enabled.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/73f36363061b46ae. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/query/scan/ScanResultValue.java:103

  public Object getEvents()
  {
    return events;
  }

  @Nullable
  @JsonProperty
  public RowSignature getRowSignature()
  {
    return rowSignature;
  }


  public long getFirstEventTimestamp(ScanQuery.ResultFormat resultFormat)
  {
    if (resultFormat.equals(ScanQuery.ResultFormat.RESULT_FORMAT_LIST)) {
      Object timestampObj = ((Map<String, Object>) ((List<Object>) this.getEvents()).get(0)).get(ColumnHolder.TIME_COLUMN_NAME);
      if (timestampObj == null) {
        throw new ISE("Unable to compare timestamp for rows without a time column");
      }
      return DimensionHandlerUtils.convertObjectToLong(timestampObj);
    } else if (resultFormat.equals(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)) {
      int timeColumnIndex = this.getColumns().indexOf(ColumnHolder.TIME_COLUMN_NAME);
      if (timeColumnIndex == -1) {
        throw new ISE("Unable to compare timestamp for rows without a time column");
      }
      List<Object> firstEvent = (List<Object>) ((List<Object>) this.getEvents()).get(0);
      return DimensionHandlerUtils.convertObjectToLong(firstEvent.get(timeColumnIndex));
    }
    throw new UOE("Unable to get first event timestamp using result format of [%s]", resultFormat.toString());
  }

  public List<ScanResultValue> toSingleEventScanResultValues()
  {
    List<ScanResultValue> singleEventScanResultValues = new ArrayList<>();
    List<Object> events = (List<Object>) this.getEvents();
    for (Object event : events) {

View on GitHub (pinned to 9b90983fd2)