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

  1. Keep exactly one of uris, prefixes, or objects in the inputSource spec and delete the others.
  2. If none is set, add the appropriate property for your storage (e.g. "prefixes": ["s3://bucket/path/"]).
  3. 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

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


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)