{"record":{"id":"0c2ddf3c829168da","repo":"pinpoint-apm/pinpoint","slug":"otlptraceparseexception-syntax-error-message-bui","errorCode":null,"errorMessage":"OtlpTraceParseException (syntax error, message built from syntaxErrorMessage(e))","messagePattern":"OtlpTraceParseException \\(syntax error, message built from syntaxErrorMessage\\(e\\)\\)","errorType":"http","errorClass":"OtlpTraceParseException","httpStatus":400,"severity":"error","filePath":"otlptrace/otlptrace-collector/src/main/java/com/navercorp/pinpoint/otlp/trace/collector/controller/OtlpJsonTraceParser.java","lineNumber":75,"sourceCode":"    private static final Set<String> ID_FIELD_NAMES = Set.of(\n            \"traceId\", \"trace_id\",\n            \"spanId\", \"span_id\",\n            \"parentSpanId\", \"parent_span_id\");\n\n    private static final JsonFactory JSON_FACTORY = new JsonFactory();\n    private static final JsonFormat.Parser PROTO_JSON_PARSER = JsonFormat.parser().ignoringUnknownFields();\n\n    private OtlpJsonTraceParser() {\n    }\n\n    public static ExportTraceServiceRequest parse(byte[] body) {\n        try {\n            final String protoJson = rewriteIdsToBase64(body);\n            final ExportTraceServiceRequest.Builder builder = ExportTraceServiceRequest.newBuilder();\n            PROTO_JSON_PARSER.merge(protoJson, builder);\n            return builder.build();\n        } catch (JsonProcessingException e) {\n            throw new OtlpTraceParseException(syntaxErrorMessage(e), e);\n        } catch (IOException | IllegalArgumentException e) {\n            throw new OtlpTraceParseException(e.getMessage(), e);\n        }\n    }\n\n    /**\n     * Jackson's {@link JsonProcessingException#getMessage()} appends the full {@code JsonLocation}\n     * ({@code [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1,\n     * column: 19]}), sometimes twice, which more than doubles the message without adding anything a\n     * client can act on. Keep the cause and the position only — in the shape the OTel collector's\n     * own JSON errors take.\n     */\n    static String syntaxErrorMessage(JsonProcessingException e) {\n        // The original message can embed a location too (\"(for Array starting at [Source: ...])\").\n        final String original = SOURCE_LOCATION.matcher(e.getOriginalMessage()).replaceAll(\"line $1, column $2\");\n        final JsonLocation location = e.getLocation();\n        if (location == null) {\n            return original;","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/otlptrace/otlptrace-collector/src/main/java/com/navercorp/pinpoint/otlp/trace/collector/controller/OtlpJsonTraceParser.java#L57-L93","documentation":"OtlpJsonTraceParser.parse() parses the OTLP/HTTP JSON body into an ExportTraceServiceRequest protobuf via Jackson's ProtoJson parser. A JsonProcessingException (malformed JSON, wrong types, invalid field names) is wrapped as OtlpTraceParseException with a syntax-error message. The request is rejected because it is not valid OTLP JSON.","triggerScenarios":"POSTing a trace export request to the OTLP JSON trace collector endpoint whose body fails JSON parsing: malformed JSON syntax, unknown fields, wrong value types (e.g. string where bytes expected), or IDs encoded other than base64.","commonSituations":"SDKs or scripts hand-rolling OTLP JSON instead of using protobuf JSON serialization; proxies mangling the body; base64/protobuf binary body sent to the JSON endpoint; truncated request bodies.","solutions":["Validate the request body with a strict JSON parser and a proto JSON schema check before sending; generate the body with ExportTraceServiceRequest JSON serialization rather than hand-built JSON.","Confirm the client targets the JSON endpoint with Content-Type: application/json (binary protobuf goes to the protobuf endpoint).","Use the error's syntax message (line/column info from Jackson) to locate the malformed portion of the payload.","Ensure all trace/span IDs are base64-encoded when using the JSON format (the server rewrites ids to base64 before parsing)."],"exampleFix":"// before\nString json = \"{\\\"resourceSpans\\\": [\" + partial + \"}\"; // hand-built, truncated\n// after\nString json = JsonFormat.printer().omittingInsignificantWhitespace().print(exportRequest);","handlingStrategy":"validation","validationCode":"ObjectMapper om = new ObjectMapper();\ntry (JsonParser p = om.createParser(body)) {\n    while (p.nextToken() != null) {} // syntax check before sending\n}","typeGuard":"static boolean looksLikeOtlpJson(String body) {\n    return body != null && body.trim().startsWith(\"{\") && body.contains(\"resourceSpans\");\n}","tryCatchPattern":"try {\n    byte[] resp = sendOtlpJson(body);\n} catch (OtlpTraceParseException e) {\n    log.error(\"OTLP JSON rejected: {}\", e.getMessage());\n    // fix payload or switch to protobuf binary endpoint\n}","preventionTips":["Serialize OTLP requests with the official protobuf JSON printer, never hand-built JSON","Send JSON only to the JSON endpoint with Content-Type: application/json","Base64-encode all bytes/ID fields as required by proto3 JSON mapping","Validate payloads against the opentelemetry-proto schema in CI"],"tags":["java","otlp","json","protobuf","http"],"backgroundTag":"json-parse-error","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"}