How to Generate UUID in Go: Complete Guide with Examples

Understanding UUIDs in Go Development

Universally Unique Identifiers (UUIDs) are essential in Go applications for creating unique identifiers across distributed systems without coordination. Go, with its focus on simplicity and performance, provides several excellent libraries for UUID generation, each offering different features and performance characteristics suitable for various use cases.

This comprehensive guide explores multiple approaches to generate UUIDs in Go applications, from using popular libraries to implementing custom solutions. Whether you're building microservices, APIs, or distributed systems, understanding how to properly implement and leverage UUIDs in Go is crucial for modern application development.

4 Effective Methods to Generate UUIDs in Go

Method 1: Using google/uuid Package

The google/uuid package is one of the most popular and well-maintained UUID libraries for Go. It provides a simple API and supports all UUID versions defined in RFC 4122.

package main

import (
    "fmt"
    "log"
    
    "github.com/google/uuid"
)

func main() {
    // Generate a new random UUID (Version 4)
    id := uuid.New()
    fmt.Printf("Generated UUID: %s\n", id)
    
    // Generate UUID as string directly
    idString := uuid.NewString()
    fmt.Printf("UUID as string: %s\n", idString)
    
    // Generate nil UUID
    nilUUID := uuid.Nil
    fmt.Printf("Nil UUID: %s\n", nilUUID)
    
    // Parse a UUID from string
    parsed, err := uuid.Parse("550e8400-e29b-41d4-a716-446655440000")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Parsed UUID: %s\n", parsed)
}

The google/uuid package provides a clean and straightforward API. The uuid.New() function generates a Version 4 (random) UUID, while uuid.NewString() returns it directly as a string. The package also includes parsing capabilities and predefined constants like uuid.Nil for the nil UUID. This library is recommended for most Go applications due to its simplicity and Google's backing.

Method 2: Using satori/go.uuid Package

The satori/go.uuid package offers more flexibility with support for different UUID versions and additional features like UUID sorting and custom generators.

package main

import (
    "fmt"
    "log"
    
    "github.com/satori/go.uuid"
)

func main() {
    // Generate Version 1 UUID (timestamp + MAC address)
    v1, err := uuid.NewV1()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Version 1 UUID: %s\n", v1)
    
    // Generate Version 4 UUID (random)
    v4, err := uuid.NewV4()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Version 4 UUID: %s\n", v4)
    
    // Generate Version 5 UUID (namespace + name)
    v5 := uuid.NewV5(uuid.NamespaceURL, "https://example.com")
    fmt.Printf("Version 5 UUID: %s\n", v5)
    
    // Convert to different formats
    fmt.Printf("Bytes: %x\n", v4.Bytes())
    fmt.Printf("String: %s\n", v4.String())
}

The satori/go.uuid package provides explicit functions for generating different UUID versions. Version 1 UUIDs include timestamp and MAC address information, Version 4 UUIDs are random, and Version 5 UUIDs are deterministic based on namespace and name. This package is ideal when you need specific UUID versions or more control over the generation process.

Method 3: Generating Different UUID Versions

Different UUID versions serve different purposes. Here's how to generate various UUID versions using the google/uuid package, which supports Version 1, 3, 4, 5, and 7.

package main

import (
    "fmt"
    "time"
    
    "github.com/google/uuid"
)

func main() {
    // Version 1: Time-based UUID
    // Note: Requires MAC address access
    uuid.SetNodeID([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab})
    v1, _ := uuid.NewUUID()
    fmt.Printf("Version 1 (Time-based): %s\n", v1)
    
    // Version 3: MD5 hash of namespace and name
    v3 := uuid.NewMD5(uuid.NamespaceDNS, []byte("example.com"))
    fmt.Printf("Version 3 (MD5): %s\n", v3)
    
    // Version 4: Random UUID (most common)
    v4 := uuid.New()
    fmt.Printf("Version 4 (Random): %s\n", v4)
    
    // Version 5: SHA-1 hash of namespace and name
    v5 := uuid.NewSHA1(uuid.NamespaceURL, []byte("https://example.com"))
    fmt.Printf("Version 5 (SHA-1): %s\n", v5)
    
    // Version 7: Time-ordered with Unix timestamp (if supported)
    // Note: Version 7 is a newer addition and may require latest package version
    v7, _ := uuid.NewV7()
    fmt.Printf("Version 7 (Time-ordered): %s\n", v7)
    
    // Extract timestamp from Version 1 UUID
    timestamp, _ := uuid.GetTime()
    fmt.Printf("Timestamp from V1: %s\n", time.Unix(timestamp.UnixTime()))
}

Each UUID version has specific use cases: Version 1 includes timestamp and MAC address (good for debugging but may leak information), Version 3 and 5 create deterministic UUIDs from namespaces (useful for consistent identification), Version 4 is purely random (most common for general use), and Version 7 provides time-ordering with better database index performance. Choose the version based on your specific requirements.

Method 4: Custom UUID Implementation

For learning purposes or specific requirements, you can implement a basic UUID generator using Go's crypto/rand package for secure random number generation.

package main

import (
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "io"
)

// UUID represents a UUID
type UUID [16]byte

// NewUUID generates a new Version 4 UUID
func NewUUID() (UUID, error) {
    var uuid UUID
    
    // Read 16 random bytes
    _, err := io.ReadFull(rand.Reader, uuid[:])
    if err != nil {
        return uuid, err
    }
    
    // Set version (4) and variant bits according to RFC 4122
    uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
    uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant 10
    
    return uuid, nil
}

// String returns the UUID in standard format
func (u UUID) String() string {
    var buf [36]byte
    hex.Encode(buf[0:8], u[0:4])
    buf[8] = '-'
    hex.Encode(buf[9:13], u[4:6])
    buf[13] = '-'
    hex.Encode(buf[14:18], u[6:8])
    buf[18] = '-'
    hex.Encode(buf[19:23], u[8:10])
    buf[23] = '-'
    hex.Encode(buf[24:], u[10:16])
    
    return string(buf[:])
}

func main() {
    uuid, err := NewUUID()
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Custom UUID: %s\n", uuid)
}

This custom implementation demonstrates the basic structure of a Version 4 UUID generator. It uses crypto/rand for cryptographically secure random bytes, sets the appropriate version and variant bits according to RFC 4122, and formats the output in the standard UUID format. While educational, it's recommended to use well-tested libraries for production applications.

Creating a Reusable UUID Service

For applications that frequently use UUIDs, it's beneficial to create a service that centralizes UUID generation logic and provides additional utilities:

package uuidservice

import (
    "database/sql/driver"
    "fmt"
    "sync"
    
    "github.com/google/uuid"
)

// Service provides UUID generation and utility functions
type Service struct {
    mu         sync.Mutex
    generated  int64
    namespace  uuid.UUID
}

// NewService creates a new UUID service
func NewService(namespace string) (*Service, error) {
    ns, err := uuid.Parse(namespace)
    if err != nil {
        // Use a default namespace if parsing fails
        ns = uuid.NamespaceDNS
    }
    
    return &Service{
        namespace: ns,
    }, nil
}

// GenerateV4 generates a random UUID v4
func (s *Service) GenerateV4() uuid.UUID {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    s.generated++
    return uuid.New()
}

// GenerateV5 generates a deterministic UUID v5 from a name
func (s *Service) GenerateV5(name string) uuid.UUID {
    return uuid.NewSHA1(s.namespace, []byte(name))
}

// GenerateBatch generates multiple UUIDs efficiently
func (s *Service) GenerateBatch(count int) []uuid.UUID {
    uuids := make([]uuid.UUID, count)
    
    s.mu.Lock()
    defer s.mu.Unlock()
    
    for i := 0; i < count; i++ {
        uuids[i] = uuid.New()
        s.generated++
    }
    
    return uuids
}

// GetStats returns generation statistics
func (s *Service) GetStats() int64 {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    return s.generated
}

// NullableUUID handles nullable UUIDs for database operations
type NullableUUID struct {
    UUID  uuid.UUID
    Valid bool
}

// Scan implements the sql.Scanner interface
func (n *NullableUUID) Scan(value interface{}) error {
    if value == nil {
        n.UUID, n.Valid = uuid.Nil, false
        return nil
    }
    
    n.Valid = true
    return n.UUID.Scan(value)
}

// Value implements the driver.Valuer interface
func (n NullableUUID) Value() (driver.Value, error) {
    if !n.Valid {
        return nil, nil
    }
    return n.UUID.Value()
}

// Example usage
func Example() {
    service, _ := NewService("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
    
    // Generate single UUID
    id := service.GenerateV4()
    fmt.Printf("Generated: %s\n", id)
    
    // Generate deterministic UUID
    userId := service.GenerateV5("user@example.com")
    fmt.Printf("User ID: %s\n", userId)
    
    // Generate batch
    batch := service.GenerateBatch(10)
    fmt.Printf("Generated %d UUIDs\n", len(batch))
    
    // Check statistics
    fmt.Printf("Total generated: %d\n", service.GetStats())
}

This UUID service provides a centralized way to manage UUID generation in your application. It includes thread-safe generation, statistics tracking, batch generation for performance, and database-friendly nullable UUID support. The service pattern makes it easy to mock UUID generation in tests and maintain consistent UUID handling across your application.

Parsing and Validating UUIDs in Go

When accepting UUIDs from external sources, proper validation is crucial. Here's how to parse and validate UUIDs effectively in Go:

package main

import (
    "fmt"
    "regexp"
    "strings"
    
    "github.com/google/uuid"
)

// Custom UUID validation using regex
var uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$`)

// ValidateUUID validates a UUID string
func ValidateUUID(input string) (uuid.UUID, error) {
    // Try parsing with the uuid package
    parsed, err := uuid.Parse(input)
    if err != nil {
        return uuid.Nil, fmt.Errorf("invalid UUID format: %w", err)
    }
    
    // Check if it's the nil UUID
    if parsed == uuid.Nil {
        return uuid.Nil, fmt.Errorf("nil UUID not allowed")
    }
    
    return parsed, nil
}

// ValidateUUIDVersion checks if a UUID is a specific version
func ValidateUUIDVersion(id uuid.UUID, expectedVersion uuid.Version) error {
    if id.Version() != expectedVersion {
        return fmt.Errorf("expected UUID version %d, got %d", expectedVersion, id.Version())
    }
    return nil
}

// NormalizeUUID handles various UUID input formats
func NormalizeUUID(input string) (uuid.UUID, error) {
    // Remove common wrapper characters
    cleaned := strings.Trim(input, "{}()")
    
    // Remove spaces and convert to lowercase
    cleaned = strings.ToLower(strings.ReplaceAll(cleaned, " ", ""))
    
    // Handle UUID without hyphens
    if len(cleaned) == 32 && !strings.Contains(cleaned, "-") {
        // Insert hyphens at the correct positions
        cleaned = fmt.Sprintf("%s-%s-%s-%s-%s",
            cleaned[0:8],
            cleaned[8:12],
            cleaned[12:16],
            cleaned[16:20],
            cleaned[20:32],
        )
    }
    
    return uuid.Parse(cleaned)
}

func main() {
    testCases := []string{
        "550e8400-e29b-41d4-a716-446655440000",  // Valid
        "550e8400e29b41d4a716446655440000",      // Valid without hyphens
        "{550e8400-e29b-41d4-a716-446655440000}", // Valid with braces
        "not-a-uuid",                             // Invalid
        "00000000-0000-0000-0000-000000000000",  // Nil UUID
    }
    
    for _, test := range testCases {
        fmt.Printf("Testing: %s\n", test)
        
        // Basic validation
        if id, err := ValidateUUID(test); err != nil {
            fmt.Printf("  Validation failed: %v\n", err)
        } else {
            fmt.Printf("  Valid UUID: %s (Version %d)\n", id, id.Version())
        }
        
        // Try normalization
        if normalized, err := NormalizeUUID(test); err == nil {
            fmt.Printf("  Normalized: %s\n", normalized)
        }
        
        fmt.Println()
    }
}

This validation example shows multiple approaches to UUID validation in Go. The ValidateUUID function uses the uuid package's built-in parsing, while NormalizeUUID handles various input formats that users might provide. The code also demonstrates version checking and nil UUID detection. For production use, consider which validation rules are appropriate for your specific use case.

Performance Considerations and Benchmarking

When generating UUIDs at scale, understanding performance characteristics becomes important. Here's how to benchmark and optimize UUID generation in Go:

package main

import (
    "fmt"
    "runtime"
    "sync"
    "testing"
    "time"
    
    "github.com/google/uuid"
)

// BenchmarkUUIDGeneration benchmarks different UUID generation methods
func BenchmarkUUIDGeneration(b *testing.B) {
    b.Run("Sequential", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = uuid.New()
        }
    })
    
    b.Run("Parallel", func(b *testing.B) {
        b.RunParallel(func(pb *testing.PB) {
            for pb.Next() {
                _ = uuid.New()
            }
        })
    })
    
    b.Run("String", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = uuid.NewString()
        }
    })
}

// ConcurrentUUIDGenerator generates UUIDs concurrently
type ConcurrentUUIDGenerator struct {
    workers int
    buffer  chan uuid.UUID
    wg      sync.WaitGroup
    stop    chan struct{}
}

// NewConcurrentGenerator creates a new concurrent UUID generator
func NewConcurrentGenerator(workers, bufferSize int) *ConcurrentUUIDGenerator {
    g := &ConcurrentUUIDGenerator{
        workers: workers,
        buffer:  make(chan uuid.UUID, bufferSize),
        stop:    make(chan struct{}),
    }
    
    g.start()
    return g
}

// start begins the worker goroutines
func (g *ConcurrentUUIDGenerator) start() {
    g.wg.Add(g.workers)
    
    for i := 0; i < g.workers; i++ {
        go func() {
            defer g.wg.Done()
            
            for {
                select {
                case <-g.stop:
                    return
                case g.buffer <- uuid.New():
                    // UUID generated and sent to buffer
                }
            }
        }()
    }
}

// Get retrieves a UUID from the generator
func (g *ConcurrentUUIDGenerator) Get() uuid.UUID {
    return <-g.buffer
}

// Stop gracefully stops the generator
func (g *ConcurrentUUIDGenerator) Stop() {
    close(g.stop)
    g.wg.Wait()
    close(g.buffer)
}

func main() {
    // Performance comparison
    fmt.Println("UUID Generation Performance Test")
    fmt.Printf("CPU cores: %d\n\n", runtime.NumCPU())
    
    count := 1_000_000
    
    // Sequential generation
    start := time.Now()
    for i := 0; i < count; i++ {
        _ = uuid.New()
    }
    sequential := time.Since(start)
    fmt.Printf("Sequential: %v for %d UUIDs\n", sequential, count)
    fmt.Printf("Rate: %.0f UUIDs/second\n\n", float64(count)/sequential.Seconds())
    
    // Concurrent generation
    generator := NewConcurrentGenerator(runtime.NumCPU(), 1000)
    start = time.Now()
    
    for i := 0; i < count; i++ {
        _ = generator.Get()
    }
    
    concurrent := time.Since(start)
    generator.Stop()
    
    fmt.Printf("Concurrent: %v for %d UUIDs\n", concurrent, count)
    fmt.Printf("Rate: %.0f UUIDs/second\n", float64(count)/concurrent.Seconds())
    fmt.Printf("Speedup: %.2fx\n", sequential.Seconds()/concurrent.Seconds())
    
    // Memory usage estimation
    fmt.Printf("\nMemory per UUID: %d bytes\n", len(uuid.New()))
    fmt.Printf("Memory for %d UUIDs: %.2f MB\n", count, float64(count*16)/(1024*1024))
}

This performance example demonstrates both benchmarking techniques and a concurrent UUID generator for high-throughput scenarios. The concurrent generator uses worker goroutines to generate UUIDs in parallel, which can significantly improve performance on multi-core systems. The benchmarks help you understand the performance characteristics of different generation methods. For most applications, the standard uuid.New() is sufficient, but for high-volume scenarios, concurrent generation can provide better throughput.

Conclusion: Best Practices for Using UUIDs in Go

When working with UUIDs in Go applications, choose the appropriate library and UUID version based on your specific requirements. The google/uuid package is recommended for most use cases due to its simplicity, performance, and maintenance by Google. For applications requiring specific UUID versions or additional features, satori/go.uuid provides more flexibility. Always validate UUIDs from external sources and consider the performance implications when generating large numbers of UUIDs.

Remember that while UUIDs provide excellent uniqueness guarantees, they come with trade-offs such as larger storage requirements (16 bytes vs 4-8 bytes for integers) and potential performance impacts on database indexes. For time-series data or when ordering is important, consider using Version 1 or Version 7 UUIDs. For general-purpose unique identifiers, Version 4 random UUIDs are the most common choice. Always handle errors appropriately and consider implementing retry logic for UUID generation in critical paths.

For quick UUID generation without writing code, you can always use our online UUID generator tool, which provides various UUID versions and formats with a simple click.

© UUIDGenerator.co v1.0 All rights reserved (2025)