ramsey/uuid · error · InvalidBytesException

Invalid number of bytes

Error message

Invalid number of bytes

What it means

VariantTrait::getVariant() reads the variant bits from the UUID's internal 16-byte string and first asserts strlen($this->getBytes()) === 16, throwing Ramsey\Uuid\Exception\InvalidBytesException ('Invalid number of bytes') when the length differs. The trait is shared by Ramsey\Uuid\Rfc4122\Fields, Guid\Fields and Nonstandard\Fields, whose constructors validate length eagerly — so reaching this throw usually means a custom Fields implementation using VariantTrait, or a byte string that was truncated/corrupted. Any byte count other than exactly 16 (15, 17, 32...) triggers it.

Source

Thrown at src/Rfc4122/VariantTrait.php:58

    /**
     * Returns the variant
     *
     * The variant number describes the layout of the UUID. The variant number has the following meaning:
     *
     * - 0 - Reserved for NCS backward compatibility
     * - 2 - The RFC 9562 (formerly RFC 4122) variant
     * - 6 - Reserved, Microsoft Corporation backward compatibility
     * - 7 - Reserved for future definition
     *
     * For RFC 9562 (formerly RFC 4122) variant UUIDs, this value should always be the integer `2`.
     *
     * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
     */
    public function getVariant(): int
    {
        if (strlen($this->getBytes()) !== 16) {
            throw new InvalidBytesException('Invalid number of bytes');
        }

        // According to RFC 9562, sections {@link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 4.1} and
        // {@link https://www.rfc-editor.org/rfc/rfc9562#section-5.10 5.10}, the Max UUID falls within the range
        // of the future variant.
        if ($this->isMax()) {
            return Uuid::RESERVED_FUTURE;
        }

        // According to RFC 9562, sections {@link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 4.1} and
        // {@link https://www.rfc-editor.org/rfc/rfc9562#section-5.9 5.9}, the Nil UUID falls within the range
        // of the Apollo NCS variant.
        if ($this->isNil()) {
            return Uuid::RESERVED_NCS;
        }

        /** @var int[] $parts */
        $parts = unpack('n*', $this->getBytes());

View on GitHub (pinned to da5b521600)

Solutions

  1. Ensure the byte string is exactly 16 bytes before constructing fields: pad hex to 32 characters with str_pad($hex, 32, '0', STR_PAD_LEFT) before hex2bin()
  2. Prefer Uuid::fromBytes($bytes) or Uuid::fromString($string) — their validators reject wrong lengths with a clearer error up front
  3. In a custom Fields implementation, validate strlen($bytes) === 16 in the constructor so the error surfaces at creation time
  4. Check the storage schema (BINARY(16)) and any transport encoding that might truncate the value

Example fix

// before: hex string is 30 chars -> 15 bytes
$bytes = hex2bin('e4b8adce621111e3b9a19b01aaa2');
$fields = new CustomFields($bytes);
$variant = $fields->getVariant(); // InvalidBytesException: Invalid number of bytes

// after: pad to 32 hex chars so the byte string is exactly 16 bytes
$bytes = hex2bin(str_pad('e4b8adce621111e3b9a19b01aaa2', 32, '0', STR_PAD_LEFT));
$fields = new CustomFields($bytes);
$variant = $fields->getVariant(); // 2 (RFC 9562 variant)
Defensive patterns

Strategy: validation

Validate before calling

// Run before building fields from raw bytes
if (strlen($bytes) !== 16) {
    throw new \InvalidArgumentException(
        sprintf('UUID bytes must be exactly 16 bytes, got %d', strlen($bytes))
    );
}

// Or when starting from hex, pad/validate first
$hex = str_pad($hex, 32, '0', STR_PAD_LEFT);
if (strlen($hex) !== 32 || !ctype_xdigit($hex)) {
    throw new \InvalidArgumentException('Expected a 32-character hex string');
}
$bytes = hex2bin($hex);

Type guard

function isUuidByteString(string $bytes): bool
{
    return strlen($bytes) === 16;
}

Try / catch

use Ramsey\Uuid\Exception\InvalidBytesException;

try {
    $variant = $fields->getVariant();
} catch (InvalidBytesException $e) {
    // reject/re-request the input; do not guess a variant
}

Prevention

When it happens

Trigger: Calling getVariant() on fields built from a byte string that is not exactly 16 bytes — e.g. hex2bin() of a 30-character hex string (15 bytes), substr($bytes, 0, 15) truncation in a custom codec, or a custom Fields class using VariantTrait without validating its input length.

Common situations: Binary UUID columns truncated by a database (CHAR(15) instead of BINARY(16)); custom builders on GUID systems reordering 16 bytes with substr offsets; hand-built hex pads shorter than 32 characters; bitwise copy/paste errors in byte manipulation code.

Related errors


AI-assisted analysis of ramsey/uuid@da5b521600 (2026-08-21). Data as JSON: /api/errors/72cc3e203948606c. Report an issue: GitHub.