How to validate UUIDs properly
May 16, 2026 · 18 min read
A regex that checks 36 characters is where UUID validation starts, not where it ends. Production bugs slip through when version nibbles are wrong, variant bits are invalid, or Microsoft endianness turns the same logical ID into two different byte arrays.
Layers of validation
- Syntactic - length, hyphens, hex charset.
- Structural - version nibble in {1-8}, variant bits per RFC.
- Semantic - matches generator policy (e.g. only v4 allowed).
- Binary - round-trip string → bytes → string unchanged.
const UUID_RE =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
function isUuidSyntax(s) {
return UUID_RE.test(s.trim());
}
Note the [1-8] version slot and [89ab] variant slot - stricter than naive hex-only patterns.
Nil and max UUID edge cases
The nil UUID 00000000-0000-0000-0000-000000000000 is valid syntactically but often rejected as "unset."
Document whether your API accepts nil or treats it as null. Same for all-f sentinel values in legacy data.
Validate at system boundaries
- API gateway - reject malformed IDs before database work.
- ORM hooks - validate client-supplied IDs on PUT/PATCH.
- ETL - quarantine rows that fail binary round-trip.
import uuid
def validate_uuid(value: str) -> uuid.UUID:
return uuid.UUID(value.strip()) # raises ValueError if invalid
Validation in Java, Go, and SQL
UUID parsed = UUID.fromString(input.trim());
// Invalid format -> IllegalArgumentException
u, err := uuid.Parse(strings.TrimSpace(input))
if err != nil { /* reject */ }
-- PostgreSQL rejects bad strings at cast time
SELECT 'not-a-uuid'::uuid; -- ERROR
Framework middleware (Express, FastAPI, ASP.NET) should validate path parameters before hitting ORMs. Returning
400 Bad Request for malformed IDs saves database round-trips and makes client bugs obvious in metrics.
Don't trust client-generated IDs blindly
Even valid v4 UUIDs can be supplied by an attacker. Validation ensures shape; authorization ensures the caller may reference that resource. For write endpoints, consider server-side generation only.
OpenAPI and JSON Schema patterns
Document UUID fields with type: string and format: uuid in OpenAPI 3. Many code generators
emit native UUID types from that hint. For JSON Schema draft 2020-12, the format keyword is informative;
still validate server-side because clients ignore schemas.
# OpenAPI fragment
components:
schemas:
UserId:
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
GraphQL custom scalars should parse on input and serialize canonical lowercase on output. Add tests for uppercase, braced, and compact hex if your API claims to accept them.
FAQ
- Is uppercase UUID invalid?
- No. Canonical comparisons should be case-insensitive for hex digits.
- Are UUIDs with braces invalid?
- Braced form is common in .NET. Strip { } before strict RFC validation or accept both in your parser.
- How do I validate MongoDB Binary UUID?
- Decode Extended JSON subtype 3 or 4 and run binary validation. See our MongoDB UUID converter.
- Should I allow compact 32-hex without hyphens?
- Decide per API. If yes, normalize to canonical form before storage. Document the accepted shapes in OpenAPI.
- What HTTP status for invalid UUID?
- 400 for malformed syntax; 404 when format is valid but resource does not exist (avoid leaking existence via 400 vs 404 debates).
Related: Common UUID mistakes