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.
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>.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
