signalapp/Signal-Server · error · ClientErrorException

exceeded maximum uploadLength

Error message

exceeded maximum uploadLength

What it means

AttachmentControllerV4.getAttachmentUploadForm validates the requested uploadLength against the server-configured maximum. If the requested byte size exceeds maxUploadLength, it returns HTTP 413 'exceeded maximum uploadLength' and no upload form/credentials are issued.

Solutions

  1. Check file size client-side and refuse/trim uploads above the known max before requesting a form
  2. Reduce the attachment size (compress, downscale video, split files) and retry
  3. If the limit is unexpectedly low, verify the server's maxUploadLength configuration

Example fix

// before
long size = file.length();
String form = api.getAttachmentUploadForm(size);
// after
if (file.length() > MAX_UPLOAD_LENGTH) {
  throw new FileTooLargeException(file.length(), MAX_UPLOAD_LENGTH);
}
String form = api.getAttachmentUploadForm(file.length());
Defensive patterns

Strategy: validation

Validate before calling

if (file.length() > MAX_UPLOAD_LENGTH) { throw new IllegalArgumentException("file exceeds max upload length: " + file.length()); }

Try / catch

try { requestUploadForm(size); } catch (WebApplicationException e) { if (e.getResponse().getStatus() == 413) { compressOrReject(file); } }

Prevention

When it happens

Trigger: GET the attachment upload form with ?uploadLength=N where N > server maxUploadLength; large video/file uploads where the client does not clamp the size before requesting a form.

Common situations: Uploading media larger than the deployment's configured cap; server max lowered via config while clients still attempt old large uploads; client sending bytes instead of the expected unit.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV4.java:111

          Uploads with the returned form will be limited to a maximum size of the provided uploadLength.
          """
  )
  @ApiResponse(responseCode = "200", description = "Success, response body includes upload form", useReturnTypeSchema = true)
  @ApiResponse(responseCode = "400", description = "The provided uploadLength was not valid")
  @ApiResponse(responseCode = "413", description = "The provided uploadLength is larger than the maximum supported upload size. The maximum upload size is subject to change and is governed by `global.attachments.maxBytes`.")
  @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  public AttachmentDescriptorV3 getAttachmentUploadForm(
      @Auth AuthenticatedDevice auth,
      @Parameter(description = "The size of the attachment to upload in bytes")
      @QueryParam("uploadLength") final @Valid Optional<@Positive Long> maybeUploadLength,
      @HeaderParam(HttpHeaders.USER_AGENT) @Nullable final String userAgent)
      throws RateLimitExceededException {

    final long uploadLength = maybeUploadLength.orElse(maxUploadLength);
    if (uploadLength > maxUploadLength) {
      throw new ClientErrorException("exceeded maximum uploadLength", Response.Status.REQUEST_ENTITY_TOO_LARGE);
    }

    countRateLimiter.validate(auth.accountIdentifier());
    if (maybeUploadLength.isPresent()) {
      // Ideally we'd check these two rate limits transactionally and only update them if both permits were acquired.
      // However, just undoing the first modification if the second one fails is close enough for our purposes
      try {
        bytesRateLimiter.validate(auth.accountIdentifier(), maybeUploadLength.get());
      } catch (RateLimitExceededException e) {
        countRateLimiter.restorePermits(auth.accountIdentifier(), 1);
        throw e;
      }
    }

    DistributionSummary.builder(ATTACHMENT_SIZE_NAME)
        .tags(Tags.of(UserAgentTagUtil.getPlatformTag(userAgent),
            Tag.of("uploadLengthSupplied", Boolean.toString(maybeUploadLength.isPresent()))))
        .register(Metrics.globalRegistry)

View on GitHub (pinned to 100ab61c82)