signalapp/Signal-Server · error · BadRequestException

Invalid length

Error message

Invalid length

What it means

After performing the media copy, the controller maps the CopyResult outcome SOURCE_WRONG_LENGTH to BadRequestException("Invalid length"): the source object stored on the CDN has a size that differs from the expected length supplied in the copy request, so the integrity-checked copy is refused.

Solutions

  1. Re-fetch the attachment's actual object size from the CDN and correct the length in CopyMediaObject before copying.
  2. Re-upload the source attachment if the stored object is truncated or corrupted, then retry the copy.
  3. Refresh local attachment metadata (size, digest) from the message sender or server.
  4. If the source is unrecoverable, skip the copy and treat the attachment as missing.

Example fix

// before
copyRequest = new CopyMediaObject(oldMetadata.size, ...); // stale size -> 400 Invalid length
// after
long actualSize = headObject(sourceCdnKey).contentLength();
copyRequest = new CopyMediaObject(actualSize, ...);
Defensive patterns

Strategy: try-catch

Validate before calling

long actualSize = headSourceObject(cdn, key).contentLength();
if (actualSize != expectedLength) {
  refreshMetadataThenCopy(); // fix length before calling copyMedia
}

Try / catch

try {
  copyMedia(request);
} catch (BadRequestException e) {
  if ("Invalid length".equals(e.getMessage())) {
    reFetchObjectSizeAndRetry();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling copyMedia with CopyMediaObject whose source length/expected plaintext length does not match the actual stored object's size.

Common situations: Client DB retaining stale attachment metadata (size recorded before a re-upload); partially uploaded or truncated source objects; migrating from an older client that recorded sizes differently; CDN objects modified or replaced.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java:770

      @NotNull
      @Valid final ArchiveController.CopyMediaRequest copyMediaRequest)
      throws BackupFailedZkAuthenticationException, BackupWrongCredentialTypeException, BackupPermissionException, BackupInvalidArgumentException {
    if (account.isPresent()) {
      throw new BadRequestException("must not use authenticated connection for anonymous operations");
    }

    final AuthenticatedBackupUser backupUser =
        backupManager.authenticateBackupUser(presentation.presentation, signature.signature, userAgent);
    final BackupManager.CopyQuota copyQuota =
        backupManager.getCopyQuota(backupUser, List.of(copyMediaRequest.toCopyParameters()), maxAttachmentSize);
    final CopyResult copyResult = backupManager.copyToBackup(copyQuota).next()
            .blockOptional()
            .orElseThrow(() -> new IllegalStateException("Non empty copy request must return result"));
    backupMetrics.updateCopyCounter(copyResult, UserAgentTagUtil.getPlatformTag(userAgent));
    return switch (copyResult.outcome()) {
      case SUCCESS -> new CopyMediaResponse(copyResult.cdn());
      case SOURCE_WRONG_LENGTH -> throw new BadRequestException("Invalid length");
      case SOURCE_NOT_FOUND -> throw new ClientErrorException("Source object not found", Response.Status.GONE);
      case OUT_OF_QUOTA ->
          throw new ClientErrorException("Media quota exhausted", Response.Status.REQUEST_ENTITY_TOO_LARGE);
    };
  }

  public record CopyMediaBatchRequest(
      @Schema(description = "A list of media objects to copy from the attachments CDN to the backup CDN")
      @NotNull
      @Size(min = 1, max = 1000)
      List<@Valid CopyMediaRequest> items) {}

  public record CopyMediaBatchResponse(

      @Schema(description = "Detailed outcome information for each copy request in the batch")
      List<Entry> responses) {

    public record Entry(

View on GitHub (pinned to 100ab61c82)