{"record":{"id":"4ad9720be061d2a6","repo":"apache/skywalking","slug":"invalid-limits","errorCode":"invalid_limits","errorMessage":"Request body is not valid JSON: {re.getMessage()}. Send an empty body for defaults, or a valid JSON object with optional `recordCap`, `retentionMillis`, `granularity` fields.","messagePattern":"Request body is not valid JSON: (.+?)\\. Send an empty body for defaults, or a valid JSON object with optional `recordCap`, `retentionMillis`, `granularity` fields\\.","errorType":"http","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"warning","filePath":"oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java","lineNumber":449,"sourceCode":"    }\n\n    /**\n     * Parses the optional JSON body. Throws {@link IllegalArgumentException}\n     * with a user-facing message on malformed JSON or out-of-range limits,\n     * which the caller maps to {@code 400 invalid_limits}. An empty body\n     * stays mapped to {@link SessionLimits#DEFAULT} so a plain\n     * {@code POST /dsl-debugging/session?...} without a body just works.\n     */\n    private SessionLimits parseLimits(final HttpData body) {\n        if (body == null || body.length() == 0) {\n            return SessionLimits.DEFAULT;\n        }\n        final JsonObject root;\n        try {\n            root = JsonParser.parseString(\n                new String(body.array(), StandardCharsets.UTF_8)).getAsJsonObject();\n        } catch (final RuntimeException re) {\n            throw new IllegalArgumentException(\n                \"Request body is not valid JSON: \" + re.getMessage()\n                    + \". Send an empty body for defaults, or a valid JSON object \"\n                    + \"with optional `recordCap`, `retentionMillis`, `granularity` \"\n                    + \"fields.\");\n        }\n        final int recordCap = root.has(\"recordCap\")\n            ? root.get(\"recordCap\").getAsInt() : SessionLimits.DEFAULT.getRecordCap();\n        final long retention = root.has(\"retentionMillis\")\n            ? root.get(\"retentionMillis\").getAsLong()\n            : SessionLimits.DEFAULT.getRetentionMillis();\n        final Granularity granularity = root.has(\"granularity\")\n            ? Granularity.ofWireName(root.get(\"granularity\").getAsString())\n            : SessionLimits.DEFAULT.getGranularity();\n        // Let SessionLimits validate the bounds — caller surfaces 400 invalid_limits.\n        return new SessionLimits(recordCap, retention, granularity);\n    }\n\n    private static JsonObject ruleKeyToJson(final RuleKey key) {","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/apache/skywalking/blob/102af09b4a56064e22050dded10e2c52e490d040/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java#L431-L467","documentation":"DSLDebuggingRestHandler.parseLimits() accepts either an empty body (defaults) or a JSON object with optional recordCap, retentionMillis, granularity fields when creating a debugging session. Gson parsing/conversion of the body throws a RuntimeException (JsonSyntaxException or IllegalStateException from getAsJsonObject) which is converted into this IllegalArgumentException, surfaced to the REST caller as an invalid_limits error.","triggerScenarios":"POST /dsl-debugging/session with a body that is not valid JSON (truncated JSON, stray quotes, form-encoded payload), or valid JSON that is not an object (array, bare string/number); sending Content-Type JSON with an empty-but-present body containing whitespace also fails parsing.","commonSituations":"Calling the debugging REST API with curl and misquoted payloads; proxies or clients that mangle the body; automated scripts generating JSON via string concatenation with unescaped characters; sending a JSON array of limit objects instead of one object.","solutions":["Send no body at all to accept defaults, or a well-formed JSON object: {\"recordCap\": 100, \"retentionMillis\": 300000, \"granularity\": \"...\"}","Validate the payload with a JSON linter or jq before sending; pass it via a file: curl --data-binary @limits.json","Ensure the client sends raw JSON (Content-Type: application/json) and not form-encoding"],"exampleFix":"# before\ncurl -X POST 'http://oap:8092/dsl-debugging/session?...' -d 'recordCap=100'\n# after\ncurl -X POST 'http://oap:8092/dsl-debugging/session?...' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"recordCap\":100,\"retentionMillis\":300000}'","handlingStrategy":"validation","validationCode":"// Client-side: build the body with a JSON library, never string concat\nObjectNode n = JsonNodeFactory.instance.objectNode();\nif (recordCap > 0) n.put(\"recordCap\", recordCap);\nif (retentionMillis > 0) n.put(\"retentionMillis\", retentionMillis);\nString body = n.isEmpty() ? \"\" : n.toString();","typeGuard":"function isLimitsBody(v: unknown): v is { recordCap?: number; retentionMillis?: number; granularity?: string } {\n  return v === undefined || (typeof v === 'object' && v !== null && !Array.isArray(v));\n}","tryCatchPattern":"REST clients should treat the 4xx invalid_limits response as terminal client error: surface the message (it embeds the Gson parse failure and the accepted fields) and do not retry with the same body.","preventionTips":["Send no body for defaults instead of an empty object","Use a JSON serializer (jq, jackson, gson) to construct the payload; never hand-concatenate JSON"],"tags":["dsl-debugging","rest","json","validation"],"backgroundTag":null,"analyzedSha":"102af09b4a56064e22050dded10e2c52e490d040","analyzedAt":"2026-08-14T10:47:52.647Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}