{"record":{"id":"b856e81e392c754d","repo":"signalapp/Signal-Server","slug":"could-not-parse-already-validated-number","errorCode":null,"errorMessage":"could not parse already validated number","messagePattern":"could not parse already validated number","errorType":"http","errorClass":"ServerErrorException","httpStatus":500,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java","lineNumber":207,"sourceCode":"          \"\"\")\n  @ApiResponse(responseCode = \"200\", description = \"The verification session was created successfully\", useReturnTypeSchema = true)\n  @ApiResponse(responseCode = \"422\", description = \"The request did not pass validation\")\n  @ApiResponse(responseCode = \"429\", description = \"Too many attempts\", headers = @Header(\n      name = \"Retry-After\",\n      description = \"If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed\",\n      schema = @Schema(implementation = Integer.class)))\n  public VerificationSessionResponse createSession(@NotNull @Valid final CreateVerificationSessionRequest request,\n      @Context final ContainerRequestContext requestContext)\n      throws RateLimitExceededException, ObsoletePhoneNumberFormatException {\n\n    final Pair<String, PushNotification.TokenType> pushTokenAndType = validateAndExtractPushToken(\n        request.updateVerificationSessionRequest());\n\n    final Phonenumber.PhoneNumber phoneNumber;\n    try {\n      phoneNumber = Util.canonicalizePhoneNumber(PhoneNumberUtil.getInstance().parse(request.number(), null));\n    } catch (final NumberParseException e) {\n      throw new ServerErrorException(\"could not parse already validated number\", Response.Status.INTERNAL_SERVER_ERROR);\n    }\n\n    Optional<CarrierData> maybeCarrierData;\n\n    if (dynamicConfigurationManager.getConfiguration().getCarrierDataLookupConfiguration().enabled()) {\n      try {\n        maybeCarrierData = carrierDataProvider.lookupCarrierData(phoneNumber,\n            dynamicConfigurationManager.getConfiguration().getCarrierDataLookupConfiguration().maxCacheAge());\n      } catch (final IOException | CarrierDataException e) {\n        logger.warn(\"Failed to retrieve carrier data\", e);\n        maybeCarrierData = Optional.empty();\n      }\n    } else {\n      maybeCarrierData = Optional.empty();\n    }\n\n    final RegistrationServiceSession registrationServiceSession =\n        registrationServiceClient.createRegistrationSession(phoneNumber,","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java#L189-L225","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the exact number value sent in the request; ensure it is in E.164 format (e.g. +14155552671) before calling the endpoint.","Check server-side Util.canonicalizePhoneNumber and libphonenumber version for a regression or mismatch with the validation path.","Log the NumberParseException in createSession to identify the failing parse mode and fix the offending stored/forwarded number.","If you operate the service, normalize numbers to E.164 at ingestion so parse+canonicalize cannot fail."],"exampleFix":"// before\nString number = request.number(); // e.g. \"(415) 555-2671\"\n// after\nString number = \"+14155552671\"; // E.164, pre-canonicalized client-side","handlingStrategy":"validation","validationCode":"// libphonenumber-js\nconst parsed = parsePhoneNumberFromString(number);\nif (!parsed || !parsed.isValid()) throw new Error('send E.164 number');\nconst e164 = parsed.number; // e.g. \"+14155552671\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always send E.164-formatted numbers","Validate client-side with libphonenumber before the API call","Keep client/server libphonenumber versions aligned"],"tags":["phone-number","internal-error","parsing","http-500"],"backgroundTag":"internal-invariant-violation","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"}