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
- Ensure only String, String[], or List<String> values are placed into the PartitionBoundaries set
- Coerce numeric or other boundary values to strings before inserting (e.g. String.valueOf(boundary))
- If boundaries come from serialized input, decode them back to Strings before constructing PartitionBoundaries
- 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
- Only insert String/String[]/List<String> into PartitionBoundaries
- Coerce numbers/other types with String.valueOf before insertion
- Validate deserialized boundary payloads before building the set
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
- Output type[%s] must be numeric
- Cannot deserialize type[%s] to an RoaringBitmap64Counter:
- Cannot add / to a Spectator Histogram
- Expected a Number, but received [%s] of type [%s]
- Expected a number or an instance of MergingDigest, but recei
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/cabb4cdb99a57268.
Report an issue: GitHub.