{"record":{"id":"f18a757a17f5d081","repo":"signalapp/Signal-Server","slug":"return-response-status-411","errorCode":null,"errorMessage":"return Response.status(411)","messagePattern":"return Response\\.status\\(411\\)","errorType":"http","errorClass":"DeviceLimitExceededException","httpStatus":411,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/mappers/DeviceLimitExceededExceptionMapper.java","lineNumber":19,"sourceCode":"/*\n * Copyright 2013-2020 Signal Messenger, LLC\n * SPDX-License-Identifier: AGPL-3.0-only\n */\n\npackage org.whispersystems.textsecuregcm.mappers;\n\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport jakarta.ws.rs.core.Response;\nimport jakarta.ws.rs.ext.ExceptionMapper;\nimport jakarta.ws.rs.ext.Provider;\nimport org.whispersystems.textsecuregcm.controllers.DeviceLimitExceededException;\n\n@Provider\npublic class DeviceLimitExceededExceptionMapper implements ExceptionMapper<DeviceLimitExceededException> {\n  @Override\n  public Response toResponse(DeviceLimitExceededException exception) {\n    return Response.status(411)\n                   .entity(new DeviceLimitExceededDetails(exception.getCurrentDevices(),\n                                                          exception.getMaxDevices()))\n                   .build();\n  }\n\n  private static class DeviceLimitExceededDetails {\n    @JsonProperty\n    private int current;\n    @JsonProperty\n    private int max;\n\n    public DeviceLimitExceededDetails(int current, int max) {\n      this.current = current;\n      this.max     = max;\n    }\n  }\n}\n","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/mappers/DeviceLimitExceededExceptionMapper.java#L1-L37","documentation":"The DeviceLimitExceededExceptionMapper converts a DeviceLimitExceededException into HTTP 411 (Length Required — Signal reuses this code) with a JSON body of DeviceLimitExceededDetails containing currentDevices and maxDevices. It signals the account has hit the server's maximum number of linked devices, so a new device link/provision request is rejected.","triggerScenarios":"Any endpoint that throws DeviceLimitExceededException — typically attempting to provision/link a new device when the account already has maxDevices linked devices.","commonSituations":"Users accumulating dormant linked desktops/tablets over years; automations repeatedly provisioning test devices against one account; low maxDevices setting in server config making the limit easy to hit.","solutions":["Unlink existing devices (via the primary device's linked-devices screen or the device API) before linking a new one","Reuse an existing linked device instead of provisioning another","Raise the max device limit in server configuration if legitimate use requires it","Audit for scripts leaking device provisioning calls in a loop"],"exampleFix":"// before\nlinkNewDevice(account);\n// after\nif (deviceList.size() >= maxDevices) {\n  unlinkOldestDevice(deviceList);\n}\nlinkNewDevice(account);","handlingStrategy":"validation","validationCode":"// count linked devices before attempting to provision a new one\nif (account.getDevices().size() >= serverMaxDevices) {\n  throw new IllegalStateException(\"unlink a device first\");\n}","typeGuard":"boolean canLinkDevice(Account a, int maxDevices) {\n  return a.getDevices().size() < maxDevices;\n}","tryCatchPattern":"try { linkDevice(account); }\ncatch (DeviceLimitExceededException e) {\n  showUserDeviceList(e.getCurrentDevices(), e.getMaxDevices()); // prompt unlink\n}","preventionTips":["Surface the linked-device list and limit in your UI","Unlink dormant desktop/tablet sessions periodically","Avoid automated loops that provision devices","Raise server max-devices config only deliberately"],"tags":["http","devices","limit-exceeded","jaxrs"],"backgroundTag":"value-out-of-range","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"}