{"record":{"id":"97af1c8d7ef35578","repo":"signalapp/Signal-Server","slug":"return-response-status-422-build","errorCode":null,"errorMessage":"return Response.status(422).build();","messagePattern":"return Response\\.status\\(422\\)\\.build\\(\\);","errorType":"http","errorClass":null,"httpStatus":422,"severity":"warning","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/mappers/JsonMappingExceptionMapper.java","lineNumber":18,"sourceCode":"package org.whispersystems.textsecuregcm.mappers;\n\nimport com.fasterxml.jackson.databind.JsonMappingException;\nimport jakarta.ws.rs.core.Response;\nimport jakarta.ws.rs.ext.ExceptionMapper;\n\npublic class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {\n  @Override\n  public Response toResponse(final JsonMappingException exception) {\n    if (exception.getCause() instanceof java.util.concurrent.TimeoutException) {\n      return Response.status(Response.Status.REQUEST_TIMEOUT).build();\n    }\n    if (exception.getCause() instanceof org.eclipse.jetty.io.EofException\n        || exception.getMessage() != null && exception.getMessage().startsWith(\"Early EOF\")) {\n      // Some sort of timeout or broken connection\n      return Response.status(Response.Status.BAD_REQUEST).build();\n    }\n    return Response.status(422).build();\n  }\n}\n","sourceCodeStart":1,"sourceCodeEnd":21,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/mappers/JsonMappingExceptionMapper.java#L1-L21","documentation":"JsonMappingExceptionMapper handles Jackson JsonMappingExceptions during request deserialization. Client-abort cases (Jetty EofException or 'Early EOF' messages) map to 400 Bad Request; every other mapping failure — meaning the JSON was syntactically parsed but did not match the target type — returns HTTP 422 Unprocessable Entity.","triggerScenarios":"Sending a request body whose JSON shape doesn't fit the endpoint's DTO: wrong field types, missing required properties handled as mapping errors, or enum/object coercion failures.","commonSituations":"API clients sending old/changed payload schemas after a server upgrade; hand-rolled JSON missing nested objects; clients truncating bodies (that path instead yields 400 via Early EOF); content-type mismatches producing wrong binding.","solutions":["Validate the request JSON against the endpoint's expected schema before sending (correct field names and types)","Update the client to match the server's current API model after version changes","Capture the 422 and log the request body to identify the offending field","If a valid payload is rejected, check custom Jackson deserializers/annotations on the DTO"],"exampleFix":"// before\nRequest.Body(\"{\"deviceId\": \"17\"}\"); // deviceId as string, DTO expects int\n// after\nRequest.Body(\"{\"deviceId\": 17}\"); // match DTO field types","handlingStrategy":"validation","validationCode":"// validate the payload shape before sending\nObjectNode body = mapper.valueToTree(dto);\nmapper.readerFor(dto.getClass()).readValue(mapper.writeValueAsBytes(body)); // dry-run roundtrip","typeGuard":null,"tryCatchPattern":"if (response.code() == 422) {\n  log.error(\"payload rejected, body=\" + lastRequestBody);\n  refreshApiModelAndReserialize();\n} else if (response.code() == 400) {\n  reconnect(); // early EOF / truncated body\n}","preventionTips":["Serialize DTOs through the same Jackson version as the server's API model","Dry-run deserialize payloads client-side before sending","Keep client/server API schemas in sync after upgrades","Detect truncated uploads (Early EOF -> 400) and retry whole-body sends"],"tags":["http","json","deserialization","jaxrs"],"backgroundTag":"schema-validation-failed","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}