signalapp/Signal-Server · error · WebApplicationException
Missing required device capability
Error message
Missing required device capability
What it means
DeviceController.linkDevice rejects linking a new device that does not advertise the OPTIONAL_PHONE_NUMBER capability when the account has no PhoneNumberIdentifier (PNI). The capability is required because the new device must support accounts without a PNI; without it the server returns 409 with this message before validating PNI keys.
Solutions
- Update the linking client to declare DeviceCapability.OPTIONAL_PHONE_NUMBER in its capabilities set
- Upgrade the client app to a version that supports phone-number identifiers
- If you control the account state, ensure the account has a PNI or use a client matching the account's requirements
- Verify the capabilities payload sent during device activation includes optional-phone-number
Example fix
// before Set<DeviceCapability> caps = EnumSet.of(DeviceCapability.STORAGE); // missing OPTIONAL_PHONE_NUMBER linkDevice(account, new LinkDeviceRequest(attrs(caps), ...)); // 409 Missing required device capability // after Set<DeviceCapability> caps = EnumSet.of(DeviceCapability.STORAGE, DeviceCapability.OPTIONAL_PHONE_NUMBER); linkDevice(account, new LinkDeviceRequest(attrs(caps), ...));
Defensive patterns
Strategy: validation
Validate before calling
if (account.getPhoneNumberIdentifier().isEmpty()
&& !deviceAttributes.capabilities().contains(DeviceCapability.OPTIONAL_PHONE_NUMBER)) {
// upgrade client or add capability before attempting to link
} Try / catch
try {
client.linkDevice(account, request);
} catch (WebApplicationException e) {
if (e.getResponse().getStatus() == 409) {
// add DeviceCapability.OPTIONAL_PHONE_NUMBER and retry
} else throw e;
} Prevention
- Always advertise OPTIONAL_PHONE_NUMBER in new clients
- Test linking against PNI-less accounts
- Keep client capability flags current with server expectations
When it happens
Trigger: Calling the link-device (PUT /v1/devices/link) flow for an account whose getPhoneNumberIdentifier() is empty, while the submitted linkDeviceRequest's deviceAttributes.capabilities() does not contain DeviceCapability.OPTIONAL_PHONE_NUMBER.
Common situations: Older or third-party clients that predate PNI support linking to a newer account created without a PNI; stale client builds that omit the optional-phone-number capability flag; accounts migrated to PNI-less state.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/aab9d32a722df3ff.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java:248
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
public LinkDeviceResponse linkDevice(@HeaderParam(HttpHeaders.AUTHORIZATION) @NotNull BasicAuthorizationHeader authorizationHeader,
@HeaderParam(HttpHeaders.USER_AGENT) @Nullable String userAgent,
@NotNull @Valid LinkDeviceRequest linkDeviceRequest)
throws RateLimitExceededException, DeviceLimitExceededException {
final Account account = accounts.checkDeviceLinkingToken(linkDeviceRequest.verificationCode())
.flatMap(accounts::getByAccountIdentifier)
.orElseThrow(ForbiddenException::new);
final DeviceActivationRequest deviceActivationRequest = linkDeviceRequest.deviceActivationRequest();
final DeviceAttributes deviceAttributes = linkDeviceRequest.deviceAttributes();
rateLimiters.getVerifyDeviceLimiter().validate(account.getAccountIdentifier());
// Check the optional-phone-number capability before checking PNI keys, so we can give a better error code (since an
// older device will improperly supply PNI keys for a PNI-less account)
if (account.getPhoneNumberIdentifier().isEmpty() &&
!linkDeviceRequest.deviceAttributes().capabilities().contains(DeviceCapability.OPTIONAL_PHONE_NUMBER)) {
throw new WebApplicationException("Missing required device capability", 409);
}
final boolean allKeysValid =
PreKeySignatureValidator
.validatePreKeySignatures(account.getAccountIdentityKey(),
List.of(deviceActivationRequest.aciSignedPreKey(), deviceActivationRequest.aciPqLastResortPreKey()),
userAgent,
"link-device")
&& account.getPhoneNumberIdentityKey()
.map(pniIdentityKey ->
deviceActivationRequest.pniSignedPreKey().isPresent()
&& deviceActivationRequest.pniPqLastResortPreKey().isPresent()
&& PreKeySignatureValidator.validatePreKeySignatures(
pniIdentityKey,
List.of(deviceActivationRequest.pniSignedPreKey().get(), deviceActivationRequest.pniPqLastResortPreKey().get()),
userAgent,
"link-device"))
.orElse(View on GitHub (pinned to 100ab61c82)