{"record":{"id":"a785ae2ba28af6a2","repo":"signalapp/Signal-Server","slug":"empty-body-not-allowed","errorCode":null,"errorMessage":"Empty body not allowed","messagePattern":"Empty body not allowed","errorType":"http","errorClass":"NoContentException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/providers/MultiRecipientMessageProvider.java","lineNumber":52,"sourceCode":"  public static final int MAX_RECIPIENT_COUNT = 5000;\n  public static final int MAX_MESSAGE_SIZE = Math.toIntExact(32 + DataSizeUnit.KIBIBYTES.toBytes(256));\n\n  private static final DistributionSummary RECIPIENT_COUNT_DISTRIBUTION = DistributionSummary\n      .builder(name(MultiRecipientMessageProvider.class, \"recipients\"))\n      .register(Metrics.globalRegistry);\n\n  @Override\n  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {\n    return MEDIA_TYPE.equals(mediaType.toString()) && SealedSenderMultiRecipientMessage.class.isAssignableFrom(type);\n  }\n\n  @Override\n  public SealedSenderMultiRecipientMessage readFrom(Class<SealedSenderMultiRecipientMessage> type, Type genericType, Annotation[] annotations,\n      MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)\n      throws IOException, WebApplicationException {\n    byte[] fullMessage = entityStream.readNBytes(MAX_MESSAGE_SIZE + MAX_RECIPIENT_COUNT * 100);\n    if (fullMessage.length == 0) {\n      throw new NoContentException(\"Empty body not allowed\");\n    }\n\n    try {\n      final SealedSenderMultiRecipientMessage message = SealedSenderMultiRecipientMessage.parse(fullMessage);\n      RECIPIENT_COUNT_DISTRIBUTION.record(message.getRecipients().size());\n      return message;\n    } catch (InvalidMessageException | InvalidVersionException e) {\n      throw new BadRequestException(e);\n    }\n  }\n}\n","sourceCodeStart":34,"sourceCodeEnd":64,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/providers/MultiRecipientMessageProvider.java#L34-L64","documentation":"MultiRecipientMessageProvider.readFrom (a JAX-RS message body reader for SealedSenderMultiRecipientMessage) throws NoContentException when the request body is zero bytes. An empty body cannot be a valid sealed sender multi-recipient message, so the provider fails fast with an HTTP 204-style NoContentException instead of a parse error.","triggerScenarios":"PUT/POST of a multi-recipient message endpoint with Content-Length: 0 or no body, so entityStream.readNBytes(...) returns an empty array.","commonSituations":"HTTP clients that send headers but forget the body; proxies/gateways stripping bodies; test requests built with empty payloads; clients that serialize nothing on failure.","solutions":["Send a non-empty serialized SealedSenderMultiRecipientMessage as the request body","Check the HTTP client that the payload is attached and Content-Length/Transfer-Encoding is correct","Inspect intermediaries (proxies, load balancers) that may drop empty or streamed bodies"],"exampleFix":"// before\nawait fetch('/v1/messages/multi', {method: 'PUT', headers: {'Content-Type': 'application/vnd.signal.protocol'}}); // no body\n// after\nawait fetch('/v1/messages/multi', {method: 'PUT', headers: {'Content-Type': 'application/vnd.signal.protocol'}, body: serializedMessageBytes});","handlingStrategy":"validation","validationCode":"const body = serializeMultiRecipientMessage(msg); if (!body || body.length === 0) { throw new Error('Refusing to send empty multi-recipient message body'); }","typeGuard":"null","tryCatchPattern":"try { await putMultiRecipientMessage(body); } catch (e) { if (e.status === 204 || /Empty body/.test(e.message)) { logError('serialized body was empty — check serializer'); } }","preventionTips":["Assert the serialized message is non-empty before every send","Verify Content-Length/Transfer-Encoding is set by your HTTP client","Test message serialization in unit tests so empty output fails locally"],"tags":["http","request-body","validation"],"backgroundTag":"empty-required-field","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"}