signalapp/Signal-Server · error · ServerErrorException

could not parse already validated number

Error message

could not parse already validated number

What it means

createSession throws this ServerErrorException (HTTP 500) when a phone number that was already validated upstream cannot be re-parsed or canonicalized by Util.canonicalizePhoneNumber after PhoneNumberUtil.parse. Since the number was previously validated, this indicates an internal invariant violation rather than bad client input. It signals corrupted or malformed session data on the server side.

Solutions

  1. Inspect the exact number value sent in the request; ensure it is in E.164 format (e.g. +14155552671) before calling the endpoint.
  2. Check server-side Util.canonicalizePhoneNumber and libphonenumber version for a regression or mismatch with the validation path.
  3. Log the NumberParseException in createSession to identify the failing parse mode and fix the offending stored/forwarded number.
  4. If you operate the service, normalize numbers to E.164 at ingestion so parse+canonicalize cannot fail.

Example fix

// before
String number = request.number(); // e.g. "(415) 555-2671"
// after
String number = "+14155552671"; // E.164, pre-canonicalized client-side
Defensive patterns

Strategy: validation

Validate before calling

// libphonenumber-js
const parsed = parsePhoneNumberFromString(number);
if (!parsed || !parsed.isValid()) throw new Error('send E.164 number');
const e164 = parsed.number; // e.g. "+14155552671"

Prevention

When it happens

Trigger: POST to the verification session creation endpoint where request.number() parses during initial validation but Util.canonicalizePhoneNumber(PhoneNumberUtil.getInstance().parse(request.number(), null)) throws NumberParseException — i.e. the canonicalization/parse round-trip fails on an already-accepted number format.

Common situations: Numbers stored or forwarded in a format the server's libphonenumber instance cannot re-parse (unusual regional formats, invalid region hints, whitespace/unicode artifacts); version drift between validation and canonicalization code paths; a client sending a number form that passed earlier validation but is not canonical.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java:207

          """)
  @ApiResponse(responseCode = "200", description = "The verification session was created successfully", useReturnTypeSchema = true)
  @ApiResponse(responseCode = "422", description = "The request did not pass validation")
  @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",
      schema = @Schema(implementation = Integer.class)))
  public VerificationSessionResponse createSession(@NotNull @Valid final CreateVerificationSessionRequest request,
      @Context final ContainerRequestContext requestContext)
      throws RateLimitExceededException, ObsoletePhoneNumberFormatException {

    final Pair<String, PushNotification.TokenType> pushTokenAndType = validateAndExtractPushToken(
        request.updateVerificationSessionRequest());

    final Phonenumber.PhoneNumber phoneNumber;
    try {
      phoneNumber = Util.canonicalizePhoneNumber(PhoneNumberUtil.getInstance().parse(request.number(), null));
    } catch (final NumberParseException e) {
      throw new ServerErrorException("could not parse already validated number", Response.Status.INTERNAL_SERVER_ERROR);
    }

    Optional<CarrierData> maybeCarrierData;

    if (dynamicConfigurationManager.getConfiguration().getCarrierDataLookupConfiguration().enabled()) {
      try {
        maybeCarrierData = carrierDataProvider.lookupCarrierData(phoneNumber,
            dynamicConfigurationManager.getConfiguration().getCarrierDataLookupConfiguration().maxCacheAge());
      } catch (final IOException | CarrierDataException e) {
        logger.warn("Failed to retrieve carrier data", e);
        maybeCarrierData = Optional.empty();
      }
    } else {
      maybeCarrierData = Optional.empty();
    }

    final RegistrationServiceSession registrationServiceSession =
        registrationServiceClient.createRegistrationSession(phoneNumber,

View on GitHub (pinned to 100ab61c82)