{"record":{"id":"b003f020ed5639b8","repo":"openzipkin/zipkin","slug":"calls-were-empty","errorCode":null,"errorMessage":"calls were empty","messagePattern":"calls were empty","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"zipkin/src/main/java/zipkin2/internal/AggregateCall.java","lineNumber":27,"sourceCode":"import java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport zipkin2.Call;\nimport zipkin2.Callback;\n\n/**\n * A call that blocks on others to complete before invoking a callback or returning from {@link\n * #execute()}. The first error will be returned upstream, later ones will be suppressed.\n *\n * @param <I> the type of returned from {@link Call#execute()}\n * @param <O> the type representing the aggregate success value\n */\npublic abstract class AggregateCall<I, O> extends Call.Base<O> {\n\n  public static Call<Void> newVoidCall(List<Call<Void>> calls) {\n    if (calls.isEmpty()) throw new IllegalArgumentException(\"calls were empty\");\n    if (calls.size() == 1) return calls.get(0);\n    return new AggregateVoidCall(calls);\n  }\n\n  static final class AggregateVoidCall extends AggregateCall<Void, Void> {\n    AggregateVoidCall(List<Call<Void>> calls) {\n      super(calls);\n    }\n\n    @Override protected Void newOutput() {\n      return null;\n    }\n\n    @Override protected void append(Void input, Void output) {\n    }\n\n    @Override public AggregateVoidCall clone() {\n      return new AggregateVoidCall(cloneCalls());","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/openzipkin/zipkin/blob/878ce2a1fad54ca941d17fdcf2e1d924b148eb1f/zipkin/src/main/java/zipkin2/internal/AggregateCall.java#L9-L45","documentation":"AggregateCall.newVoidCall(List<Call<Void>>) aggregates zero or more void calls and throws IllegalArgumentException('calls were empty') when the list is empty — an aggregate with nothing to wait on has no defined success value (its execute() would have to block on nothing or return a fabricated result). With exactly one call it returns that call directly; only 2+ calls are wrapped.","triggerScenarios":"Building a storage-layer bulk operation (e.g. SpanStore accepts/aggregates) from a fan-out list that happens to be empty — say, zero client addresses to check or an empty consumer batch passed to a compound call.","commonSituations":"Custom zipkin-server storage backends or collectors that aggregate per-index calls (Elasticsearch/Cassandra-style fan-out) and hit empty batches during low traffic or after filtering removes all items.","solutions":["Guard for the empty case before aggregating: return a no-op Call (e.g. Call.create(null)) instead of calling newVoidCall.","Skip issuing the aggregate operation entirely when the batch is empty.","Ensure upstream batching cannot deliver empty lists (reporters should not flush zero spans)."],"exampleFix":"// before\nreturn AggregateCall.newVoidCall(calls); // calls may be []\n\n// after\nreturn calls.isEmpty() ? Call.create(null) : AggregateCall.newVoidCall(calls);","handlingStrategy":"validation","validationCode":"Call<Void> aggregate = calls.isEmpty()\n    ? Call.create(null)\n    : AggregateCall.newVoidCall(calls);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Guard every fan-out aggregation for the empty-batch case.","Make upstream batching skip empty flushes."],"tags":["zipkin","storage","aggregate","validation"],"backgroundTag":null,"analyzedSha":"878ce2a1fad54ca941d17fdcf2e1d924b148eb1f","analyzedAt":"2026-08-14T15:17:09.895Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}