{"record":{"id":"c8843adf58de15b0","repo":"pinpoint-apm/pinpoint","slug":"otlp-http-decompressed-request-body-exceeded-max-s","errorCode":null,"errorMessage":"OTLP/HTTP decompressed request body exceeded max size: limit=","messagePattern":"OTLP/HTTP decompressed request body exceeded max size: limit=","errorType":"http","errorClass":"IOException","httpStatus":413,"severity":"error","filePath":"otlptrace/otlptrace-collector/src/main/java/com/navercorp/pinpoint/otlp/trace/collector/controller/OtlpTraceDecompressionFilter.java","lineNumber":197,"sourceCode":"     * The {@link GZIPInputStream} is constructed eagerly, so a body that is not valid gzip fails here\n     * (surfaces to the protobuf converter as a 400). Async reads are not supported (OTLP ingestion is\n     * a blocking read through the message converter).\n     */\n    private static final class GzipLimitedServletInputStream extends ServletInputStream {\n        private final GZIPInputStream gzip;\n        private final long limit;\n        private long count;\n        private boolean finished;\n\n        private GzipLimitedServletInputStream(InputStream compressed, long limit) throws IOException {\n            this.gzip = new GZIPInputStream(compressed);\n            this.limit = limit;\n        }\n\n        private void add(int read) throws IOException {\n            count += read;\n            if (count > limit) {\n                throw new IOException(\"OTLP/HTTP decompressed request body exceeded max size: limit=\" + limit);\n            }\n        }\n\n        @Override\n        public int read() throws IOException {\n            final int b = gzip.read();\n            if (b < 0) {\n                finished = true;\n                return -1;\n            }\n            add(1);\n            return b;\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            final int n = gzip.read(b, off, len);\n            if (n < 0) {","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/otlptrace/otlptrace-collector/src/main/java/com/navercorp/pinpoint/otlp/trace/collector/controller/OtlpTraceDecompressionFilter.java#L179-L215","documentation":"OtlpTraceDecompressionFilter wraps the gzip-inflated request body in a counting stream; the add() method throws IOException as soon as the decompressed byte count exceeds the configured limit. This protects the collector from decompression bombs: a small gzipped body can expand far beyond acceptable memory/disk usage.","triggerScenarios":"Sending a gzip- (or deflate-) compressed OTLP/HTTP trace request whose UNCOMPRESSED size exceeds the collector's configured max decompressed body size; the limit is hit while the counting InputStream's read() pumps bytes.","commonSituations":"A batch-exporting SDK with a huge in-memory queue flushes an oversized payload; misconfigured compression where a client compresses already-huge batches; decompression-bomb protection tripping on a legitimately large but legitimate batch; lowering the collector limit without adjusting client batch sizes.","solutions":["Reduce client-side batch/export size (max export batch size, export interval) so each compressed request inflates under the limit.","Raise the collector's decompressed body size limit configuration to accommodate legitimate payloads, keeping DoS protection in mind.","Check for compression configuration mistakes (e.g. double compression or compressing already-large payloads unnecessarily).","Handle HTTP 4xx response on the client and retry with smaller batches rather than resending the identical oversized payload."],"exampleFix":"// before\nBatchSpanProcessor.builder().setMaxExportBatchSize(8192).build(); // inflates past limit\n// after\nBatchSpanProcessor.builder().setMaxExportBatchSize(512).build();","handlingStrategy":"retry","validationCode":"int inflatedSize = batchSize * avgSpanBytes;\nif (inflatedSize > collectorMaxDecompressedBytes) {\n    splitBatch();\n}","typeGuard":"boolean underDecompressionLimit(byte[] gzipped, long limit, double worstCaseRatio) {\n    return gzipped.length * worstCaseRatio <= limit;\n}","tryCatchPattern":"try {\n    export(batch);\n} catch (HttpException e) {\n    if (isDecompressionLimitResponse(e)) {\n        for (List<SpanData> half : splitInHalf(batch)) export(half);\n    }\n}","preventionTips":["Keep client export batch sizes well under the collector's decompressed limit","Align client max-batch-size config with the server's limit when deploying both","Split large queues into multiple exports instead of one flush","Monitor rejected-request metrics to catch limit regressions early"],"tags":["java","otlp","http","compression","payload-size"],"backgroundTag":"payload-too-large","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}