Spring Boot 3.5 Embraces UUIDv7: Replace Auto-Increment IDs with Time-Ordered UUIDs

This tutorial demonstrates how to replace auto-increment IDs with UUIDv7 in Spring Boot 3.5 using JPA and MyBatis-Plus, covering UUIDv7's timestamp-based structure, the java-uuid-generator library, and custom identifier generator implementations for both persistence frameworks.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Boot 3.5 Embraces UUIDv7: Replace Auto-Increment IDs with Time-Ordered UUIDs

UUIDv4 relies on pure randomness, making it unordered and causing database B-tree index fragmentation, page splits, and poor cache efficiency. It also lacks temporal information, requiring extra timestamp columns for sorting and debugging.

UUIDv7 (RFC 9562) places a 48-bit millisecond Unix timestamp in the most significant bits, enabling natural lexicographic ordering that aligns with database index writes. A 12-bit sub-millisecond sequence counter guarantees monotonic increments within the same millisecond, while 62 random bits provide strong global collision resistance. The version field (4 bits) is fixed to 7 and the variant field (2 bits) to binary 10 per RFC 9562.

UUIDv7 Structure

Unix Timestamp — 48 bits: millisecond timestamp in MSB, establishes time ordering

Version — 4 bits: fixed to 7 (0111) identifying UUIDv7

Sub-millisecond Sequence — 12 bits: monotonic counter within a millisecond

Variant — 2 bits: fixed to 2 (10) per RFC 9562/4122

Random — 62 bits: strong pseudo-random entropy for collision resistance

Dependency

<dependency>
  <groupId>com.fasterxml.uuid</groupId>
  <artifactId>java-uuid-generator</artifactId>
  <version>5.1.0</version>
</dependency>

The library (JUG) implements RFC 9562 and supports UUID versions 1, 3, 4, 5, 6, and 7.

Quick Start

// Version 1 (time-based)
UUID uuid = Generators.timeBasedGenerator().generate();
// Version 4 (random)
uuid = Generators.randomBasedGenerator().generate();
// Version 5 (name-based hash)
uuid = Generators.nameBasedGenerator().generate("string to hash");
// Version 6 (reordered time-based)
uuid = Generators.timeBasedReorderedGenerator().generate();
// Version 7 (Unix epoch timestamp)
uuid = Generators.timeBasedEpochGenerator().generate();
// Version 7 with independent random per generation
uuid = Generators.timeBasedEpochRandomGenerator().generate();

For reuse, retain the generator instance:

TimeBasedGenerator gen = Generators.timeBasedGenerator(EthernetAddress.fromInterface());
UUID uuid = gen.generate();
UUID anotherUuid = gen.generate();

JPA Custom Annotation Strategy

Define a custom annotation @Tuid that references a generator supplier:

@IdGeneratorType(TuidGenerator.class)
@ValueGenerationType(generatedBy = TuidValueGenerator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface Tuid {
  Class<? extends Supplier<TimeBasedEpochGenerator>> value() default FactorySupplier.class;
  class FactorySupplier implements Supplier<TimeBasedEpochGenerator> {
    public static final FactorySupplier INSTANCE = new FactorySupplier();
    private TimeBasedEpochGenerator tuidFactory = Generators.timeBasedEpochGenerator();
    @Override
    public TimeBasedEpochGenerator get() { return tuidFactory; }
  }
}

Implement IdentifierGenerator for @Id fields:

public class TuidGenerator implements IdentifierGenerator {
  private final TimeBasedEpochGenerator tuidFactory;
  public TuidGenerator(Tuid config, Member idMember, CustomIdGeneratorCreationContext creationContext) {
    Class<? extends Supplier<TimeBasedEpochGenerator>> tuidSupplierClass = config.value();
    if (tuidSupplierClass.equals(Tuid.FactorySupplier.class)) {
      tuidFactory = Tuid.FactorySupplier.INSTANCE.get();
    } else {
      Supplier<TimeBasedEpochGenerator> factorySupplier;
      try { factorySupplier = tuidSupplierClass.getConstructor().newInstance(); }
      catch (Exception e) { throw new RuntimeException(e); }
      tuidFactory = factorySupplier.get();
    }
  }
  @Override
  public Object generate(SharedSessionContractImplementor session, Object object) {
    return tuidFactory.generate().toString();
  }
}

Implement AnnotationBasedGenerator + BeforeExecutionGenerator for non- @Id fields:

public class TuidValueGenerator implements AnnotationBasedGenerator<Annotation>, BeforeExecutionGenerator {
  private TimeBasedEpochGenerator tuidFactory;
  @Override
  public EnumSet<EventType> getEventTypes() { return EventTypeSets.INSERT_ONLY; }
  @Override
  public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
    return tuidFactory.generate().toString();
  }
  @Override
  public void initialize(Annotation annotation, Member member, GeneratorCreationContext context) {
    if (annotation instanceof Tuid tuid) {
      Class<? extends Supplier<TimeBasedEpochGenerator>> supplierClass = tuid.value();
      if (supplierClass.equals(Tuid.FactorySupplier.class)) {
        this.tuidFactory = Tuid.FactorySupplier.INSTANCE.get();
      } else {
        try { this.tuidFactory = ((Supplier<TimeBasedEpochGenerator>) supplierClass.getConstructor().newInstance()).get(); }
        catch (Exception e) { throw new RuntimeException(e); }
      }
    }
  }
}

Usage in entity:

@Entity
@Table(name = "x_user")
public class User {
  @Id
  @Tuid
  private String id;
  private String name;
  private Integer age;
  @Tuid
  private String sno;
}

MyBatis-Plus Custom Generator

Entity uses IdType.ASSIGN_UUID:

@TableName("x_user")
public class User {
  @TableId(type = IdType.ASSIGN_UUID)
  private String id;
  private String name;
  private Integer age;
  private String sno;
}

Implement IdentifierGenerator:

@Component
public class UuidV7Generator implements IdentifierGenerator {
  @Override
  public Number nextId(Object entity) { return null; }
  @Override
  public String nextUUID(Object entity) {
    return Generators.timeBasedEpochGenerator().generate().toString();
  }
}

Mapper extends BaseMapper<User>.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Distributed SystemsSpring BootMyBatis-PlusJPAUUIDv7Primary Key Generationjava-uuid-generatorRFC 9562
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.