{"record":{"id":"dd366b551216bc86","repo":"redis/jedis","slug":"aggregation-requires-exactly-one-spec-per-key-num","errorCode":null,"errorMessage":"Aggregation requires exactly one spec per key: numkeys=${numKeys} but got ${aggregators.length} aggregation spec(s)","messagePattern":"Aggregation requires exactly one spec per key: numkeys=(.+?) but got (.+?) aggregation spec\\(s\\)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/main/java/redis/clients/jedis/timeseries/TSNRangeParams.java","lineNumber":197,"sourceCode":"    }\n    this.aggregators = aggregators;\n    this.bucketDuration = bucketDuration;\n    return this;\n  }\n\n  /**\n   * Validates this aggregation configuration against the actual number of command keys.\n   * {@code TS.NRANGE}/{@code TS.NREVRANGE} require exactly one aggregation spec per key, so when\n   * aggregation is set the number of specs must equal {@code numKeys}. This mirrors the server rule\n   * client-side so callers fail fast instead of relying on the {@code the number of AGGREGATION\n   * arguments must be equal to numkeys} error. No-op when aggregation is not set.\n   * @param numKeys number of keys passed to the command\n   * @throws IllegalArgumentException if aggregation is set and the spec count differs from\n   *           {@code numKeys}\n   */\n  public void validateAggregationForKeys(int numKeys) {\n    if (aggregators != null && aggregators.length != numKeys) {\n      throw new IllegalArgumentException(\"Aggregation requires exactly one spec per key: numkeys=\"\n          + numKeys + \" but got \" + aggregators.length + \" aggregation spec(s)\");\n    }\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSNRangeParams bucketTimestamp(String bucketTimestamp) {\n    this.bucketTimestamp = encode(bucketTimestamp);\n    return this;\n  }\n\n  /**\n   * This requires AGGREGATION.\n   */\n  public TSNRangeParams bucketTimestampLow() {\n    this.bucketTimestamp = MINUS;\n    return this;","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/timeseries/TSNRangeParams.java#L179-L215","documentation":"TSNRangeParams.validateAggregationForKeys(int numKeys) is called by UnifiedJedis for TS.MRANGE/TS.MREVRANGE when the number of keys is known. If aggregation specs were set but their count differs from numKeys, the server contract (one AGGREGATION spec per key) would be violated, so the client throws IllegalArgumentException. It is a no-op when aggregation was never set (aggregators == null).","triggerScenarios":"Executing tsNRange/tsNRevRange with N keys while TSNRangeParams.aggregation(...) was given fewer or more than N AggregationType[] specs, e.g. 3 keys with only 2 specs.","commonSituations":"Key list built dynamically at runtime while the spec array was built from a different (stale or filtered) collection; copy-pasted params objects reused across commands with different key counts.","solutions":["Align the spec array length with the number of keys: supply exactly one AggregationType[] per key before executing the command.","If the same params must serve varying key counts, build a fresh TSNRangeParams per call and size the specs to keys.size().","Call params.validateAggregationForKeys(keys.size()) yourself before executing to catch mismatches at the call site with a clear stack trace."],"exampleFix":"// before\nparams.aggregation(new AggregationType[][]{{AggregationType.AVG}}, 60000); // 1 spec\njedis.tsNRange(keys, params); // keys.size() == 3 -> throws\n// after\nAggregationType[][] specs = new AggregationType[keys.size()][];\nfor (int i = 0; i < keys.size(); i++) specs[i] = new AggregationType[]{AggregationType.AVG};\nparams.aggregation(specs, 60000);\njedis.tsNRange(keys, params);","handlingStrategy":"validation","validationCode":"if (params != null && keys.size() != expectedSpecCount) {\n  throw new IllegalStateException(\"specs=\" + expectedSpecCount + \" keys=\" + keys.size());\n}\njedis.tsNRange(keys, params);","typeGuard":null,"tryCatchPattern":"try { jedis.tsNRange(keys, params); } catch (IllegalArgumentException e) { log.error(\"aggregation spec count mismatch for {} keys\", keys.size(), e); throw e; }","preventionTips":["Build specs from the same keys collection so lengths stay equal","Call validateAggregationForKeys(keys.size()) before executing","Avoid sharing one TSNRangeParams across commands with different key counts"],"tags":["java","validation","timeseries","multi-key"],"backgroundTag":"invalid-argument-value","analyzedSha":"6dac31d4c224fb3257c216f3985340c6f500cdcb","analyzedAt":"2026-09-08T04:55:01.204Z","contentChangedAt":"2026-09-08T04:55:01.204Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}