apache/druid · error · IllegalArgumentException
Exactly one of uris, prefixes or objects must be specified
Error message
Exactly one of uris, prefixes or objects must be specified
What it means
CloudObjectInputSource requires exactly one of the three ways to identify cloud objects: uris, prefixes, or objects. The constructor validates this via throwIfIllegalArgs and throws IllegalArgumentException when zero or more than one of them is set. This prevents ambiguous input source definitions that cannot be split into tasks.
Solutions
- Keep exactly one of uris, prefixes, or objects in the inputSource spec and delete the others.
- If none is set, add the appropriate property for your storage (e.g. "prefixes": ["s3://bucket/path/"]).
- Migrate older specs: replace the deprecated uris form with objects (each with bucket/path) or prefixes.
Example fix
// before
{"type":"s3","prefixes":["s3://b/p/"],"objects":[{"bucket":"b","path":"p/f.json"}]}
// after
{"type":"s3","prefixes":["s3://b/p/"]} Defensive patterns
Strategy: validation
Validate before calling
int count = 0;
if (spec.has("uris")) count++;
if (spec.has("prefixes")) count++;
if (spec.has("objects")) count++;
if (count != 1) throw new IllegalArgumentException("Exactly one of uris, prefixes or objects must be specified"); Type guard
function assertCloudSource(src) {
const set = [src.uris, src.prefixes, src.objects].filter(x => x != null);
if (set.length !== 1) throw new Error('Exactly one of uris, prefixes or objects must be specified');
return src;
} Try / catch
try {
InputSource src = jsonMapper.readValue(specJson, InputSource.class);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Exactly one of uris")) {
throw new SpecValidationError("inputSource must define exactly one of uris/prefixes/objects", e);
}
throw e;
} Prevention
- Validate ingestion specs (druid validate or ingestion spec schema) before submission.
- When editing specs by hand, remove old listing properties instead of adding new ones alongside.
- Standardize on the objects form for new S3/GS specs and avoid mixing legacy uris.
- Unit-test input source construction for all three configuration modes.
When it happens
Trigger: Submitting an ingestion spec with a CloudObjectInputSource (S3/GS/Azure) that sets none of uris/prefixes/objects, or sets both uris and prefixes, or both prefixes and objects; programmatically building an InputSource leaving all three null.
Common situations: Hand-edited JSON ingestion specs with leftover keys after switching from URI listing to prefix listing; template-generated specs where an optional field was not removed; copying an S3 spec and adding an objects field without removing prefixes.
Related errors
- Invalid URI scheme [ ] must be [ ]
- Caching is not supported. Check `isCacheable` before…
- Cannot compare against something that is not a…
- Cannot compare against something that is not a…
- Column[ ] is not a valid column
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/574e7794e7c61422.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/data/input/impl/CloudObjectInputSource.java:318
private void illegalArgsChecker() throws IllegalArgumentException
{
if (!CollectionUtils.isNullOrEmpty(objects)) {
throwIfIllegalArgs(!CollectionUtils.isNullOrEmpty(uris) || !CollectionUtils.isNullOrEmpty(prefixes));
} else if (!CollectionUtils.isNullOrEmpty(uris)) {
throwIfIllegalArgs(!CollectionUtils.isNullOrEmpty(prefixes));
uris.forEach(uri -> CloudObjectLocation.validateUriScheme(scheme, uri));
} else if (!CollectionUtils.isNullOrEmpty(prefixes)) {
prefixes.forEach(uri -> CloudObjectLocation.validateUriScheme(scheme, uri));
} else {
throwIfIllegalArgs(true);
}
}
private void throwIfIllegalArgs(boolean clause) throws IllegalArgumentException
{
if (clause) {
throw new IllegalArgumentException("Exactly one of uris, prefixes or objects must be specified");
}
}
/**
* Stream of {@link InputSplit} for situations where this object is based on {@link #getPrefixes()}.
*
* If {@link CloudObjectSplitWidget#getDescriptorIteratorForPrefixes} returns objects with known sizes (as most
* implementations do), this method filters out empty objects.
*/
private static Stream<InputSplit<List<CloudObjectLocation>>> getSplitsForPrefixes(
final InputFormat inputFormat,
final CloudObjectSplitWidget splitWidget,
final SplitHintSpec splitHintSpec,
final List<URI> prefixes,
@Nullable final String objectGlob
)
{
Iterator<CloudObjectSplitWidget.LocationWithSize> iterator =View on GitHub (pinned to 9b90983fd2)