openzipkin/zipkin · error · NumberFormatException

should be a 1 to 32 character lower-hex string with no pref

Error message

 should be a 1 to 32 character lower-hex string with no prefix

What it means

HexCodec.lowerHexToHexString / related conversions throw NumberFormatException('<value> should be a 1 to 32 character lower-hex string with no prefix') via isntLowerHexLong when a character falls outside [0-9a-f]. This is the internal hex-parsing path for IDs: uppercase hex, '0x' prefixes, dashes (UUID style), or > 32 chars all fail. NumberFormatException (not IllegalArgumentException) is used because it is the conventional exception for bad numeric parsing.

Source

Thrown at zipkin/src/main/java/zipkin2/internal/HexCodec.java:48

   */
  public static long lowerHexToUnsignedLong(String lowerHex, int index) {
    long result = 0;
    for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) {
      char c = lowerHex.charAt(index);
      result <<= 4;
      if (c >= '0' && c <= '9') {
        result |= c - '0';
      } else if (c >= 'a' && c <= 'f') {
        result |= c - 'a' + 10;
      } else {
        throw isntLowerHexLong(lowerHex);
      }
    }
    return result;
  }

  static NumberFormatException isntLowerHexLong(String lowerHex) {
    throw new NumberFormatException(
        lowerHex + " should be a 1 to 32 character lower-hex string with no prefix");
  }

  HexCodec() {}
}

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Sanitize before parsing: strip '0x'/'-', lowercase, and validate length 1-32.
  2. For UUIDs: uuid.toString().replace("-", "") yields a valid 32-char lower-hex string.
  3. Validate with a regex ^[0-9a-f]{1,32}$ first and reject/log non-conforming inputs instead of relying on the exception.

Example fix

// before
long id = HexCodec.lowerHexToUnsignedLong(headerValue);

// after
String hex = headerValue.replace("-", "")
    .replaceFirst("^0x", "")
    .toLowerCase(Locale.ROOT);
long id = hex.matches("[0-9a-f]{1,32}")
    ? HexCodec.lowerHexToUnsignedLong(hex)
    : 0L;
Defensive patterns

Strategy: validation

Validate before calling

static final Pattern LOWER_HEX_1_32 = Pattern.compile("^[0-9a-f]{1,32}$");

boolean parsable(String s) {
  return s != null && LOWER_HEX_1_32.matcher(s).matches();
}

long parseId(String s) {
  String hex = s == null ? "" : s.replace("-", "").replaceFirst("^0x", "")
      .toLowerCase(Locale.ROOT);
  return parsable(hex) ? HexCodec.lowerHexToUnsignedLong(hex) : 0L;
}

Type guard

boolean isLowerHexId(String s) {
  return s != null && s.matches("^[0-9a-f]{1,32}$");
}

Try / catch

try {
  id = HexCodec.lowerHexToUnsignedLong(hex);
} catch (NumberFormatException e) {
  id = 0L; // treat as absent and generate a new one
}

Prevention

When it happens

Trigger: Calling HexCodec.lowerHexToHexString("ABC"), passing a UUID with dashes, a '0x'-prefixed value, or an ID longer than 32 chars; also reached indirectly from code that parses B3 header values with this codec.

Common situations: Bridging IDs from external systems (UUID-based correlation IDs, .NET Guid strings with uppercase hex) into zipkin-compatible IDs; parsing trace context headers that were mangled by proxies or logging frameworks.

Related errors


AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14). Data as JSON: /api/errors/9b72c2c7d445fcbe. Report an issue: GitHub.