{"record":{"id":"f4b35edd2edd0ec0","repo":"apache/druid","slug":"timeout-maxdefaulttimeout-must-be-a-non-negative","errorCode":null,"errorMessage":"Timeout [maxDefaultTimeout] must be a non negative value, but was %d","messagePattern":"Timeout \\[maxDefaultTimeout\\] must be a non negative value, but was (.+?)","errorType":"validation","errorClass":"BadQueryContextException","httpStatus":400,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/QueryContext.java","lineNumber":532,"sourceCode":"    );\n  }\n\n  @Nullable\n  public Duration getTimeoutDuration()\n  {\n    if (hasTimeout()) {\n      return Duration.ofMillis(getTimeout());\n    }\n    return null;\n  }\n\n  public long getDefaultTimeout()\n  {\n    final long defaultTimeout = getLong(QueryContexts.DEFAULT_TIMEOUT_KEY, QueryContexts.DEFAULT_TIMEOUT_MILLIS);\n    if (defaultTimeout >= 0) {\n      return defaultTimeout;\n    }\n    throw new BadQueryContextException(\n        StringUtils.format(\n            \"Timeout [%s] must be a non negative value, but was %d\",\n            QueryContexts.DEFAULT_TIMEOUT_KEY,\n            defaultTimeout\n        )\n    );\n  }\n\n  public void verifyMaxQueryTimeout(long maxQueryTimeout)\n  {\n    long timeout = getTimeout();\n    if (timeout > maxQueryTimeout) {\n      throw new BadQueryContextException(\n          StringUtils.format(\n              \"Configured %s = %d is more than enforced limit of %d.\",\n              QueryContexts.TIMEOUT_KEY,\n              timeout,\n              maxQueryTimeout","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/QueryContext.java#L514-L550","documentation":"Druid validates the `defaultTimeout` query-context key, which supplies the fallback query timeout. QueryContext.getDefaultTimeout throws BadQueryContextException when the configured value is negative, because a negative default timeout would produce an invalid query deadline. The literal \"maxDefaultTimeout\" in the message template is just the message's fixed label; the offending value comes from the DEFAULT_TIMEOUT_KEY context entry.","triggerScenarios":"Calling QueryContext.getDefaultTimeout() (via getTimeout() when no explicit timeout is set) when the context contains DEFAULT_TIMEOUT_KEY (\"defaultTimeout\") with a value < 0, e.g. `{\"defaultTimeout\": -1}`.","commonSituations":"Operators wiring a cluster- or tier-wide default timeout from config where a sentinel value like -1 (meaning 'unlimited' in some other system) was copied over; property-file mistakes; version changes where -1 semantics were never supported by Druid.","solutions":["Set the `defaultTimeout` context value to a non-negative millisecond value or remove it so QueryContexts.DEFAULT_TIMEOUT_MILLIS applies.","If you intend 'no timeout', omit the key rather than using -1.","Audit config generation scripts/templates for negative sentinel values leaking into query context.","Catch BadQueryContextException and re-raise with the offending context key so operators can fix config quickly."],"exampleFix":"// before\ncontext.put(\"defaultTimeout\", -1); // 'unlimited' sentinel from another system\n// after\n// omit the key entirely, or use a valid positive value\ncontext.put(\"defaultTimeout\", 300_000L);","handlingStrategy":"validation","validationCode":"Object dt = query.getContext().get(\"defaultTimeout\");\nif (dt instanceof Number && ((Number) dt).longValue() < 0) {\n    throw new IllegalArgumentException(\"defaultTimeout must be non-negative, got \" + dt);\n}","typeGuard":"boolean isValidDefaultTimeout(Object v) {\n    return !(v instanceof Number) || ((Number) v).longValue() >= 0;\n}","tryCatchPattern":"try {\n    client.query(query);\n} catch (BadQueryContextException e) {\n    if (e.getMessage().contains(\"defaultTimeout\")) {\n        query.getContext().remove(\"defaultTimeout\");\n        client.query(query); // retry with engine default\n    } else {\n        throw e;\n    }\n}","preventionTips":["Treat -1 as invalid in Druid context even if other systems use it as 'unlimited'.","Centralize query-context construction so defaults are set in one validated place.","Review config templates that feed defaultTimeout for sentinel leakage.","Log the full query context on submission failures to speed diagnosis."],"tags":["query-context","timeout","config","validation"],"backgroundTag":"value-out-of-range","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}