apache/druid · error · IllegalArgumentException

Item must either be a String or StringTuple

Error message

Item must either be a String or StringTuple

What it means

PartitionBoundaries.toStringTuple converts the underlying set items to StringTuple for display/comparison. It only supports String, String[], and List-of-String items; any other element type triggers this IllegalArgumentException. PartitionBoundaries is typed as an immutable sorted set of Object, so a wrongly-typed element surfaces here.

Source

Thrown at processing/src/main/java/org/apache/druid/timeline/partition/PartitionBoundaries.java:117

      return delegate;
    }
  }

  /**
   * Converts the given item to a StringTuple.
   */
  private StringTuple toStringTuple(Object item)
  {
    if (item == null || item instanceof StringTuple) {
      return (StringTuple) item;
    } else if (item instanceof String) {
      return StringTuple.create((String) item);
    } else if (item instanceof String[]) {
      return StringTuple.create((String[]) item);
    } else if (item instanceof List) {
      return StringTuple.create((String[]) ((List) item).toArray(new String[0]));
    } else {
      throw new IAE("Item must either be a String or StringTuple");
    }
  }

  @Override
  protected List<StringTuple> delegate()
  {
    return delegate;
  }

  public int getNumBuckets()
  {
    return delegate.size() - 1;
  }

  @Override
  public boolean equals(Object o)
  {
    if (this == o) {

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Ensure only String, String[], or List<String> values are placed into the PartitionBoundaries set
  2. Coerce numeric or other boundary values to strings before inserting (e.g. String.valueOf(boundary))
  3. If boundaries come from serialized input, decode them back to Strings before constructing PartitionBoundaries
  4. Add a constructor-time validation to reject non-string items early with a clearer message

Example fix

// before
PartitionBoundaries boundaries = new PartitionBoundaries(ImmutableSet.of(1, 2, 3));
String s = boundaries.toString(); // throws
// after
PartitionBoundaries boundaries = new PartitionBoundaries(
    ImmutableSet.of("1", "2", "3"));
String s = boundaries.toString();
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(item instanceof String) && !(item instanceof String[]) && !(item instanceof List)) {
  throw new IllegalArgumentException("Unsupported boundary type: " + item.getClass());
}

Type guard

static boolean isValidBoundaryItem(Object o) {
  return o instanceof String
      || o instanceof String[]
      || (o instanceof List && ((List<?>) o).stream().allMatch(String.class::isInstance));
}

Prevention

When it happens

Trigger: Calling toString(), or anything that iterates the boundaries through toStringTuple, on a PartitionBoundaries that was constructed (e.g. via copyOf/ImmutableSet copyOf or serialization) containing a non-String element such as byte[] or a Number.

Common situations: Passing raw partition boundary objects of the wrong type when rebuilding PartitionBoundaries from a payload; deserialization code that skipped the String-coercion step; tests inserting arbitrary objects into the set.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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