UUID vs GUID: the same 128 bits with different habits
Updated October 12, 2026 · Reading time: 6 min · 7 sections
A .NET service writes a GUID as 16 bytes, a Python job reads those bytes back, and the ID it prints matches nothing in the database. In the UUID vs GUID question the value is never the problem; the habits wrapped around it are, and byte order is the habit that bites hardest.
A GUID is a UUID
GUID, globally unique identifier, is the name Microsoft uses in COM, Windows and .NET for the 128-bit identifier that RFC 9562 defines as a UUID. Same size, same 8-4-4-4-12 text form, same version and variant fields. Guid.NewGuid() returns a random version 4 value with the RFC variant, and you can paste it into a PostgreSQL uuid column unchanged.
Three things do differ in practice: how the value is spelled, an old variant that some legacy GUIDs still carry, and how the 16 bytes are laid out in memory and on disk.
Spelling habits: capitals and braces
- Windows registry and COM write GUIDs in upper case inside braces:
{6BA7B810-9DAD-11D1-80B4-00C04FD430C8}. - SQL Server converts a
uniqueidentifierto text in upper case, without braces. - .NET
Guid.ToString()gives lower case with hyphens (format "D");ToString("B")adds braces,ToString("N")drops the hyphens,ToString("P")uses parentheses. - RFC 9562 says to output lower case and accept any case on input. PostgreSQL prints lower case and also accepts upper case and braces.
So the same identifier travels as several strings. Code that compares those strings byte by byte is where "the IDs don't match" tickets come from.
The variant digit: 8 to b versus c and d
The top bits of the 17th hex digit say which layout the rest of the value follows. Modern GUIDs use the RFC variant, but older Microsoft identifiers use their own:
| 17th digit | Variant | Where you see it |
|---|---|---|
| 0–7 | NCS, backward compatibility | Obsolete systems; also the Nil UUID |
| 8, 9, a, b | RFC 9562 (OSF DCE) | Everything modern, including Guid.NewGuid() |
| c, d | Microsoft, legacy | Old COM and DCOM identifiers |
| e, f | Reserved | Future use; the Max UUID |
The COM interface ID of IUnknown, 00000000-0000-0000-C000-000000000046, is a classic example: its 17th digit is C, so it is a Microsoft-variant GUID, and its version nibble means nothing. The Validate & decode tab of the UUID generator reports the variant for such values and does not invent a version for them.
Byte order: the swap that breaks round trips
Microsoft defines a GUID as a struct: a 32-bit integer, two 16-bit integers and 8 raw bytes. On little-endian machines those three integers are stored least significant byte first. That is why .NET Guid.ToByteArray() and the binary form of a SQL Server uniqueidentifier reverse the first three groups and keep the last two as written. RFC 9562 stores all 16 bytes in the order you read them.
| Layout | Bytes in storage order |
|---|---|
| RFC 9562 (big-endian) | 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff |
| Microsoft (mixed-endian) | 33 22 11 00 55 44 77 66 88 99 aa bb cc dd ee ff |
You can check this in Python: uuid.UUID('00112233-4455-6677-8899-aabbccddeeff').bytes_le returns the Microsoft order, and uuid.UUID(bytes_le=data) reads it back. Reading Microsoft bytes as RFC bytes gives 33221100-5544-7766-8899-aabbccddeeff: a different, perfectly valid-looking ID.
The tell-tale sign is the version digit. A random v4 GUID swapped this way has its 4 pushed to the 15th position, so the decoder reports some unexpected version. When IDs coming out of .NET suddenly decode as version 9 or b, look for a byte swap first.
The fix is to exchange text or RFC-order bytes between systems. In .NET 8 and later, id.ToByteArray(bigEndian: true) and new Guid(bytes, bigEndian: true) give you the RFC order; on older versions, pass the string.
SQL Server sorts by the last group first
A known behaviour that surprises people: when SQL Server compares two uniqueidentifier values it starts with the last group of 12 hex digits, then the fourth group, and works back towards the first. ORDER BY id therefore does not match the alphabetical order of the strings.
This matters for time-ordered keys. A v7 from Guid.CreateVersion7() carries its timestamp in the first group, the part SQL Server compares last, so it does not append neatly to a clustered index there. NEWSEQUENTIALID() produces values tailored to SQL Server's own order instead. If you want ordered inserts on SQL Server, either use that function as the column default or cluster on a sequential column and keep the GUID as a unique nonclustered key. The UUID v4 vs v7 guide covers why ordering matters for indexes.
One identifier, many spellings
Here is the DNS namespace UUID from RFC 9562 in each form you are likely to meet. Every row is the same 128-bit value.
| Format | Example | Where it shows up |
|---|---|---|
| Canonical lower case | 6ba7b810-9dad-11d1-80b4-00c04fd430c8 | RFC output, PostgreSQL, Python, .NET ToString() |
| Upper case | 6BA7B810-9DAD-11D1-80B4-00C04FD430C8 | SQL Server, Windows tools |
| Braces | {6BA7B810-9DAD-11D1-80B4-00C04FD430C8} | Registry, COM; .NET ToString("B") in lower case |
| No hyphens | 6ba7b8109dad11d180b400c04fd430c8 | .NET "N", Python .hex |
| URN | urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8 | XML and other URN-based formats |
| 0x hex, RFC order | 0x6BA7B8109DAD11D180B400C04FD430C8 | MySQL BINARY(16) literals |
| Base64, RFC order | a6e4EJ2tEdGAtADAT9QwyA== | Compact text, 24 characters |
| Hex, Microsoft order | 10b8a76bad9dd11180b400c04fd430c8 | Guid.ToByteArray(), SQL Server binary(16), Python bytes_le |
The generator's Format menu produces the first seven directly, and the decoder shows the BINARY(16) hex and Base64 of any UUID you paste. It accepts braces, urn:uuid:, 0x, missing hyphens and any case.
Comparing GUIDs and UUIDs safely
- Parse, then compare.
Guid.Parse,uuid.UUID(),UUID.fromStringand a::uuidcast all turn text into a 128-bit value; braces and case disappear. - If you have to compare strings, normalise first: strip braces and
urn:uuid:, lower-case, then compare. - Exchange text or RFC-order bytes across systems. Raw
ToByteArray()output is only safe when both ends are .NET or SQL Server. - Store native types:
uuid,uniqueidentifierorBINARY(16), not 36-character strings that double the index size.
Two more habits are worth a rule. Guid.Empty is the Nil UUID, all zeros; treat it as "no value", never as a real key, and reject it at the API boundary. And if GUIDs end up in a text column anyway, a unique index under a case-sensitive collation happily accepts 6BA7B810-… and 6ba7b810-… as two different rows. A native type closes that hole for free.
For the one-liners that create these values on each platform, see how to generate a UUID in code.
FAQ
Is a GUID the same as a UUID?
Yes. GUID is the Microsoft name for the 128-bit identifier defined in RFC 9562. The differences are habits: upper case and braces, a legacy variant on very old GUIDs, and mixed-endian byte order in .NET and SQL Server binary form.
Are GUIDs case sensitive?
No. The digits are hexadecimal, so A and a are the same value. RFC 9562 says to output lower case and accept any case; compare parsed values, not raw strings.
Why does my GUID change after converting it to bytes?
.NET Guid.ToByteArray() and SQL Server store the first three groups little-endian. Read those bytes in RFC order and you get a different ID; use text or the big-endian overload instead.
Can I store a .NET GUID in a PostgreSQL uuid column?
Yes. Send it as a string, or as RFC-order bytes with ToByteArray(bigEndian: true) in .NET 8 and later. PostgreSQL accepts upper case and braces on input and prints lower case.