About UUID Mismatch Diagnostic
This answers a different question from a converter. A converter tells you what a value is; this tells you why two values that should be the same UUID are not - which is the question people actually arrive with, usually after an hour of staring at two strings that clearly contain the same bytes in the wrong order.
The cause is almost always MongoDB's legacy binary subtype. Subtype 4 is the standard: 16 bytes in RFC 4122 order, exactly as the UUID string reads. Subtype 3 is the legacy form, and it is not one format but three, because the Java, C# and Python drivers each wrote it differently. Nothing records which one produced a given value, so the information needed to read it correctly is not in the data.
The three conventions, precisely: the Java driver reversed bytes 0-7 and bytes 8-15 separately, because it serialised the UUID's two 64-bit halves as little-endian longs. The C# driver reversed bytes 0-3, then 4-5, then 6-7, leaving the last eight alone - the Microsoft GUID memory layout, and the same bytes you get from Guid.ToByteArray(). The Python driver wrote RFC 4122 order unchanged, differing from subtype 4 only in the subtype byte.
Note that the Java transform reverses each half rather than swapping the two halves. Those are different operations, and confusing them is the most common error in write-ups of this problem - including, until recently, on this site.
Paste both values in any form - canonical UUID, 32 hex characters, Base64, a byte array, BinData(), or a $binary Extended JSON document - and this reports which convention maps one to the other, or tells you plainly that the two are genuinely different identifiers and the problem is elsewhere. Everything runs in your browser, so a real production _id is not transmitted.
How to use the UUID Mismatch Diagnostic
- Put the value your service wrote into A, and what the other service read back into B. Either order works; the diagnosis is symmetric.
- Press Run. If a byte-order convention explains the difference, it is named along with the driver setting that produces it.
- Read the table at the bottom: it shows what A's bytes mean under each of the four conventions, with the one matching B marked.
- If the verdict says the values are genuinely different, stop looking for an encoding bug - you are looking at two different records, a reused variable, or a regenerated ID.
Examples
-
Same UUID in Java legacy byte order
550e8400-e29b-41d4-a716-446655440000 -
MongoDB $binary subtype 03 as written by the Java driver
{"$binary":{"base64":"1EGb4gCEDlUAAERVZkQWpw==","subType":"03"}}
UUID Mismatch Diagnostic in code
The same operation this tool performs, in the languages you are most likely to need it.
// Given the 16 RFC 4122 bytes of a UUID:
const rfc = Buffer.from("550e8400e29b41d4a716446655440000", "hex");
// JAVA_LEGACY - reverse bytes 0-7 and 8-15 SEPARATELY.
// (The driver wrote the two 64-bit halves as little-endian longs.)
const javaLegacy = Buffer.concat([
Buffer.from(rfc.subarray(0, 8)).reverse(),
Buffer.from(rfc.subarray(8, 16)).reverse(),
]);
// d4419be200840e5500004455664416a7
// CSHARP_LEGACY - reverse bytes 0-3, 4-5 and 6-7; leave 8-15 alone.
// This is exactly Guid.ToByteArray() and Python's uuid.bytes_le.
const csharpLegacy = Buffer.concat([
Buffer.from(rfc.subarray(0, 4)).reverse(),
Buffer.from(rfc.subarray(4, 6)).reverse(),
Buffer.from(rfc.subarray(6, 8)).reverse(),
rfc.subarray(8, 16),
]);
// 00840e559be2d441a716446655440000
// PYTHON_LEGACY - identical bytes to standard; only the subtype
// byte differs (3 rather than 4).
// All three transforms are their own inverse, so the same function
// converts in either direction.
import uuid
from bson.binary import Binary, UuidRepresentation
u = uuid.UUID("550e8400-e29b-41d4-a716-446655440000")
u.bytes # STANDARD / PYTHON_LEGACY
u.bytes[7::-1] + u.bytes[:7:-1] # JAVA_LEGACY (each half reversed)
u.bytes_le # CSHARP_LEGACY (bytes 0-3, 4-5, 6-7)
# Reading a collection written by a Java service, without changing it:
from pymongo import MongoClient
java_client = MongoClient(uri, uuidRepresentation="javaLegacy")
# Reading the SAME collection as C# wrote it:
cs_client = MongoClient(uri, uuidRepresentation="csharpLegacy")
# If you get this wrong there is no error - just a different UUID.
// Take one document whose UUID you already know from elsewhere
// (a log line, an API response), then test each convention.
const known = "550e8400-e29b-41d4-a716-446655440000";
const stored = Buffer.from(doc._id.buffer); // 16 bytes as stored
const orders = {
standard: (b) => b,
javaLegacy: (b) => Buffer.concat([
Buffer.from(b.subarray(0, 8)).reverse(),
Buffer.from(b.subarray(8, 16)).reverse()]),
csharpLegacy: (b) => Buffer.concat([
Buffer.from(b.subarray(0, 4)).reverse(),
Buffer.from(b.subarray(4, 6)).reverse(),
Buffer.from(b.subarray(6, 8)).reverse(),
b.subarray(8, 16)]),
};
for (const [name, fn] of Object.entries(orders)) {
const hex = fn(stored).toString("hex");
const asUuid = hex.replace(
/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
if (asUuid === known) console.log("This collection uses:", name);
}
// The subtype is the number after the buffer
db.users.findOne({}, { _id: 1 })
// { _id: Binary(Buffer.from("d4419be2...", "hex"), 3) }
// ^ subtype 3 = legacy
// Count how many documents use each subtype - a mixed collection
// is the usual sign that a driver default changed under you
db.users.aggregate([
{ $project: { st: { $let: {
vars: { t: { $type: "$_id" } },
in: { $cond: [{ $eq: ["$$t", "binData"] }, "binary", "$$t"] } } } } },
{ $group: { _id: "$st", n: { $sum: 1 } } }
])
When you need this
- A UUID written by a Java service reads back differently in a Node or C# service.
- A query by UUID returns nothing even though you can see the document in mongosh.
- Working out which driver wrote an existing collection, before writing a migration.
- Confirming whether two values are the same UUID or genuinely two different records.
- Checking that a migration to subtype 4 converted the bytes correctly.
Common problems and what causes them
- Swapping the two halves instead of reversing each half
- JAVA_LEGACY reverses bytes 0-7 and bytes 8-15 separately. Simply exchanging the first and last eight bytes is a different transform and produces a different, wrong UUID. It is the single most common mistake in code and in blog posts about this problem.
- Assuming subtype 3 tells you the byte order
- It does not. Subtype 3 means only 'legacy UUID' - which of the three driver conventions produced it is not recorded anywhere in the data. You have to know what wrote it, which is precisely why subtype 4 exists.
- Fixing it in the reading driver and calling it done
- Setting uuidRepresentation to match the existing data makes reads correct but leaves the collection in a format only one language can interpret. If more than one service will ever touch it, migrate the data to subtype 4 once instead.
- Testing the migration against production
- _id is immutable, so converting it means insert-then-delete rather than update, plus fixing anything that referenced the old value. Run it against a restored backup first and count documents before and after.
- A mixed-subtype collection
- A driver upgrade can silently change how new UUIDs are written, leaving old documents in subtype 3 and new ones in subtype 4. Queries then work for some records and not others. Aggregate by subtype to find out before you assume the collection is uniform.
- Concluding it is a byte-order problem when it is not
- If the two values do not contain the same 16 byte values in some order, no convention will reconcile them - they are different identifiers. Look for a reused variable, a regenerated ID, or two records rather than an encoding bug.
FAQ
- Why does the same UUID look different in Java and C#?
- Because MongoDB's legacy subtype 3 has no single byte order. The Java driver reversed bytes 0-7 and 8-15 separately; the C# driver reversed bytes 0-3, 4-5 and 6-7. Same 128 bits, different arrangement on disk, and nothing in the data says which was used.
- Is JAVA_LEGACY just the two halves swapped?
- No, and this is the detail most explanations get wrong. Each 8-byte half is reversed in place. Exchanging the halves gives a different value - if your conversion is 'nearly right', this is almost certainly why.
- Why does my query by UUID find nothing?
- Your driver is encoding the query value with a different representation than the one stored, so the bytes do not match. Either set uuidRepresentation to match the stored data, or query with an explicit BinData value of the right subtype and byte order.
- How do I work out which convention my collection uses?
- Take one document whose real UUID you know from another source - a log line, an API response - and test each convention against the stored bytes until one reproduces it. The code section above does exactly that, and pasting both values here does it for you.
- Should I migrate to subtype 4?
- Yes, if more than one language will ever read the collection - the ambiguity is a permanent source of bugs. Migrate once using the byte order of the driver that wrote the data, and set uuidRepresentation=STANDARD everywhere afterwards.
- Is my production _id safe to paste here?
- Yes. This is pure byte arithmetic running in your browser - no request is made, nothing is logged, and the page works offline once loaded.