Question
How can I generate a UUID in Go?
I have code similar to the following:
u := make([]byte, 16)
_, err := rand.Read(u)
if err != nil {
return
}
u[8] = (u[8] | 0x80) & 0xBF
u[6] = (u[6] | 0x40) & 0x4F
return hex.EncodeToString(u)
The result is a 32-character hexadecimal string, but it does not look like a typical UUID with hyphens. Is it a valid UUID? Why does the code modify u[8] and u[6], and is there a better way to generate UUIDs in Go?
Short Answer
A UUID version 4 is 16 random bytes with a few bits reserved to identify its format. In Go, crypto/rand can generate the random bytes, and bitwise operations set the required version and variant bits. A 32-character hexadecimal result contains the correct 16 bytes; hyphens are only a display format. In production, a maintained UUID library is often the clearest choice.
Concept
A UUID (Universally Unique Identifier) is a 128-bit value, which is exactly 16 bytes. UUIDs are commonly displayed as hexadecimal text, such as:
550e8400-e29b-41d4-a716-446655440000
A UUID version 4 (UUIDv4) is based mostly on cryptographically secure random data. It starts with 16 random bytes, but six bits must be set to fixed values:
- The version field identifies this as version 4.
- The variant field identifies the standard UUID layout used by most software.
That leaves 122 random bits, making accidental collisions extraordinarily unlikely when a secure random source is used.
In Go, use crypto/rand, not math/rand, for UUIDv4 values. crypto/rand obtains unpredictable random bytes from the operating system, which is appropriate for identifiers that may be exposed publicly or used in security-sensitive contexts.
A UUID can be represented in more than one textual form:
550e8400e29b41d4a716446655440000 // 32 hexadecimal characters
550e8400-e29b-41d4-a716-446655440000 // conventional hyphenated form
Both contain the same 16 bytes. The hyphens do not add data; they only improve readability.
Mental Model
Think of a UUIDv4 as a 16-slot card filled with random ink.
- First, Go fills every slot randomly.
- Then the UUID rules require changing two small labels on the card:
- one label says, “This is version 4,”
- the other says, “This uses the standard UUID variant.”
The code changes only those required label bits. Almost all of the remaining bits stay random.
The hyphens in a UUID string are like spaces in a phone number: they make it easier for humans to read, but they do not change the underlying value.
Syntax and Examples
Generate 16 secure random bytes, then set the UUIDv4 version and variant fields:
package main
import (
"crypto/rand"
"fmt"
)
func newUUIDv4() (string, error) {
u := make([]byte, 16)
if _, err := rand.Read(u); err != nil {
return "", err
}
// Set version 4: 0100xxxx
u[6] = (u[6] & 0x0f) | 0x40
// Set RFC 4122 / RFC 9562 variant: 10xxxxxx
u[8] = (u[8] & 0x3f) | 0x80
return fmt.Sprintf(
"%08x-%04x-%04x-%04x-%012x",
u[0:4], u[4:6], u[6:8], u[8:10], u[10:16],
), nil
}
func main() {
id, err := newUUIDv4()
if err != nil {
panic(err)
}
fmt.Println(id)
}
Example output:
c4d21d28-dafd-4e43-a81f-2e5bf035f1db
The important bit operations are commonly written in this order:
u[6] = (u[6] & 0x0f) | 0x40
u[8] = (u[8] & 0x3f) | 0x80
&clears the bits that must be controlled.|sets the required bits.
The question's expressions are equivalent:
u[8] = (u[8] | 0x80) & 0xBF
u[6] = (u[6] | 0x40) & 0x4F
For u[8], the code ensures the two highest bits are 10. For u[6], it ensures the four highest bits are 0100. The alternative form using & first is more widely recognized and easier to verify visually.
Step by Step Execution
Consider this simplified example, where the random bytes at the required positions are:
u[6] = 0b10110111
u[8] = 0b01101101
Set the version on byte 6:
u[6] = (u[6] & 0x0f) | 0x40
Step by step:
u[6] 10110111
u[6] & 0x0f 00000111 // clear the highest four bits
0x40 01000000 // version 4 marker
result 01000111
The first four bits are now 0100, so this is version 4. The lower four bits remain random.
Set the variant on byte 8:
u[8] = (u[8] & 0x3f) | 0x80
Step by step:
u[8] 01101101
u[8] & 0x3f 00101101 // clear the highest two bits
0x80 10000000 // standard UUID variant marker
result 10101101
The first two bits are now 10, while the lower six bits remain random.
Finally, formatting the 16 bytes as hexadecimal produces 32 hex digits because every byte requires two hexadecimal characters:
16 bytes × 2 hex characters per byte = 32 hex characters
Adding hyphens creates the usual 8-4-4-4-12 display grouping without changing the UUID data.
Real World Use Cases
UUIDv4 values are useful when an application needs identifiers without asking a central database for the next number.
Common uses include:
- API resource IDs: Create an order ID or upload ID before saving the resource.
- Database primary keys: Use a UUID column when records may be created by multiple services or offline clients.
- File names and object storage keys: Avoid collisions for uploaded files, exports, or generated reports.
- Tracing and correlation: Attach an ID to a request so logs from several services can be connected.
- Invitation and public links: Generate difficult-to-guess references, while still enforcing authorization separately.
- Distributed systems: Allow independently running workers to create IDs without coordinating a shared counter.
A UUID is an identifier, not permission. Do not treat possession of a UUID alone as sufficient authorization for sensitive data.
Real Codebase Usage
In real Go projects, developers usually avoid manually formatting and validating UUIDs throughout the codebase. A common pattern is to use a maintained package, such as github.com/google/uuid.
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
id := uuid.NewString()
fmt.Println(id)
}
Install it with:
go get github.com/google/uuid
The package handles secure random generation and standard formatting.
A typical service-layer pattern creates an ID at the boundary where an entity is created:
package orders
import "github.com/google/uuid"
type Order struct {
ID uuid.UUID
Customer string
}
func NewOrder(customer string) Order {
return Order{
ID: uuid.New(),
Customer: customer,
}
}
For incoming API data, parse and validate IDs before using them:
id, err := uuid.Parse(idFromURL)
if err != nil {
return fmt.Errorf("invalid order ID: %w", err)
}
This keeps UUID generation, storage, parsing, and validation consistent. If you generate UUIDs manually, keep the generator in one well-tested function rather than repeating bit manipulation in multiple places.
Common Mistakes
Using math/rand
This is inappropriate for UUIDv4 generation when unpredictability matters:
// Do not use this for UUIDv4 generation.
import "math/rand"
value := rand.Int63()
math/rand is a deterministic pseudorandom generator. Use crypto/rand instead:
import "crypto/rand"
Forgetting the version and variant bits
This creates 16 random bytes, but not necessarily a conforming UUIDv4:
u := make([]byte, 16)
_, err := rand.Read(u)
if err != nil {
return "", err
}
return hex.EncodeToString(u), nil
Fix it by setting both required fields:
u[6] = (u[6] & 0x0f) | 0x40
u[8] = (u[8] & 0x3f) | 0x80
Reversing the bitwise operation
This does not preserve the intended random bits:
// Broken: it keeps the wrong portion of the byte.
u[6] = (u[6] & 0x40) | 0x0f
Use a mask that clears only the controlled bits, then set their required pattern.
Assuming hyphens determine validity
A 32-character hexadecimal form can represent the same UUID as a hyphenated form. However, some parsers or APIs require a particular string format. Match the format required by the receiving system.
Ignoring errors from rand.Read
Randomness can fail in rare system-level situations. Propagate the error rather than returning a partially initialized identifier.
Treating UUIDs as secrets or authorization
UUIDv4 values are hard to guess, but authorization must still be checked. For example, an API should verify that the authenticated user is allowed to access the resource identified by the UUID.
Comparisons
| Choice | Best for | Important detail |
|---|---|---|
Manual UUIDv4 with crypto/rand | Learning, small dependency-free utilities | You must correctly set bits and format output. |
github.com/google/uuid | Most Go applications | Clear API for generating, parsing, and formatting UUIDs. |
math/rand | Simulations and non-security randomness | Do not use it to generate UUIDv4 identifiers that should be unpredictable. |
| Auto-incrementing integer | One centralized database | Compact and ordered, but requires coordination and is easy to enumerate. |
| UUIDv4 | Distributed identifier generation | Random, non-sequential, and does not reveal record count. |
| UUIDv7 | Time-ordered UUID use cases | Often preferable when roughly time-sortable IDs help database indexing or ordering. |
A UUIDv4 is 128 bits of which 122 bits are random after setting the required metadata bits. It is ideal when random, decentralized ID generation matters more than chronological ordering.
Cheat Sheet
import "crypto/rand"
u := make([]byte, 16)
if _, err := rand.Read(u); err != nil {
return "", err
}
// UUID version 4: byte 6 begins with 0100.
u[6] = (u[6] & 0x0f) | 0x40
// Standard UUID variant: byte 8 begins with 10.
u[8] = (u[8] & 0x3f) | 0x80
| Item | Value |
|---|---|
| UUID size | 16 bytes / 128 bits |
| Compact hexadecimal form | 32 characters |
| Conventional string form | 8-4-4-4-12 hexadecimal groups |
| Secure Go randomness | crypto/rand.Read |
| UUIDv4 version bits | u[6] high nibble is 0100 |
| Standard variant bits | u[8] high two bits are 10 |
Equivalent masks from the original code:
u[6] = (u[6] | 0x40) & 0x4f
u[8] = (u[8] | 0x80) & 0xbf
Prefer the conventional form because its intention is clearer:
u[6] = (u[6] & 0x0f) | 0x40
u[8] = (u[8] & 0x3f) | 0x80
FAQ
Does Go's standard library provide a UUID generator?
Go's standard library provides crypto/rand for secure random bytes, but it does not provide a dedicated UUID type and UUIDv4 generator. You can implement UUIDv4 with crypto/rand or use a package such as github.com/google/uuid.
Is a 32-character hexadecimal UUID valid without hyphens?
Yes. Sixteen bytes encode as 32 hexadecimal characters. Hyphens are a conventional presentation format, although a particular API or parser may require them.
Why are bytes 6 and 8 modified for UUIDv4?
They contain the standardized metadata fields. Byte 6 is changed to indicate version 4, and byte 8 is changed to indicate the standard UUID variant.
What does (u[6] & 0x0f) | 0x40 mean?
u[6] & 0x0f clears the upper four bits. | 0x40 then sets those upper bits to 0100, the UUIDv4 version marker.
Is crypto/rand.Read safe for generating UUIDs?
Yes. It reads cryptographically secure random data from the operating system and is the correct Go random source for UUIDv4 generation.
Can UUIDv4 values collide?
Theoretically yes, because they are random. In practice, UUIDv4 has 122 random bits, so collisions are extraordinarily unlikely when generated correctly.
Should I use UUIDv4 or UUIDv7?
Use UUIDv4 for broadly compatible random identifiers. Consider UUIDv7 when time-ordered identifiers are useful, such as for improved insertion locality in some database workloads.
Are UUIDs secure access tokens?
No. A UUID can be part of a URL or resource identifier, but sensitive operations must still authenticate and authorize the caller.
Mini Project
Description
Build a small command-line Go program that generates a requested number of UUIDv4 values. The project demonstrates secure random byte generation, the required UUID version and variant bit masks, and conventional UUID formatting.
Goal
Create a program that prints one or more valid, hyphenated UUIDv4 strings.
Requirements
Use crypto/rand to fill a 16-byte slice with random data.
Set the UUIDv4 version bits and standard variant bits.
Format each value as a hyphenated 8-4-4-4-12 UUID string.
Accept an optional command-line argument for how many UUIDs to generate.
Return an error if random data cannot be read.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.