signalapp/Signal-Server · error · BadRequestException

Recipient list is empty

Error message

Recipient list is empty

What it means

Thrown by sendMultiRecipientMessage when the recipient list resolved from the sealed-sender multi-recipient payload is empty. A multi-recipient message must name at least one recipient; a request whose decoded payload contains zero recipient entries fails this validation guard and is rejected with a 400-style client error.

Solutions

  1. Check the recipient list client-side and skip the send (or surface an error) when it is empty.
  2. Rebuild the multi-recipient message ensuring all intended recipients' entries are serialized.
  3. Verify the distribution list/group actually has members at send time.

Example fix

// before
sendMultiRecipient(message); // recipients may be empty
// after
if (recipients.isEmpty()) { return; /* nothing to send */ }
sendMultiRecipient(message);
Defensive patterns

Strategy: validation

Validate before calling

if (!recipients || recipients.length === 0) return; // nothing to send, skip the API call

Type guard

function hasRecipients(recipients) { return Array.isArray(recipients) && recipients.length > 0; }

Try / catch

try { await sendMultiRecipient(msg); } catch (e) { if (e.status === 400 && /Recipient list is empty/.test(e.body)) { logSkipAndAbortSend(); } }

Prevention

When it happens

Trigger: POST to the multi-recipient endpoint with a SealedSenderMultiRecipientMessage whose getRecipients() map is empty — i.e. the serialized payload contains no recipient entries.

Common situations: Client built the message from an empty distribution list or group with all members removed; recipient filtering (e.g. skipping unsent-to users) eliminated everyone; serialization bug dropping recipients.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/11c4ce8e5238c23f. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java:496

      @Parameter(description="The sender's timestamp for the envelope")
      @QueryParam("ts") long timestamp,

      @Parameter(description="If true, this message should cause push notifications to be sent to recipients")
      @QueryParam("urgent") @DefaultValue("true") final boolean isUrgent,

      @Parameter(description="If true, the message is a story; access tokens are not checked and sending to nonexistent recipients is permitted")
      @QueryParam("story") boolean isStory,
      @Parameter(description="The sealed-sender multi-recipient message payload as serialized by libsignal")
      @NotNull SealedSenderMultiRecipientMessage multiRecipientMessage,

      @Context ContainerRequestContext context) {

    if (timestamp < 0 || timestamp > MAX_TIMESTAMP) {
      throw new BadRequestException("Illegal timestamp");
    }

    if (multiRecipientMessage.getRecipients().isEmpty()) {
      throw new BadRequestException("Recipient list is empty");
    }

    final Timer.Sample sample = Timer.start();

    try {
      final SendMultiRecipientMessageResponse sendMultiRecipientMessageResponse;

      if (isStory) {
        if (groupSendToken != null) {
          // Stories require no authentication. We fail requests that provide a groupSendToken, but for historical
          // reasons we allow requests to set a combined access key, even though we ignore it
          throw new BadRequestException("Group send token not allowed when sending stories");
        }

        sendMultiRecipientMessageResponse =
            sendMultiRecipientStoryMessage(multiRecipientMessage, timestamp, online, isUrgent, context);
      } else {
        sendMultiRecipientMessageResponse =

View on GitHub (pinned to 100ab61c82)