Generate Time‑Ordered IDs with TSID in Spring Boot 3

This guide explains why random UUIDs are inefficient as primary keys, introduces the TSID library that creates 64‑bit time‑ordered identifiers, and provides step‑by‑step Maven setup, API usage, JPA integration, and testing examples for Spring Boot 3.5.0.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Generate Time‑Ordered IDs with TSID in Spring Boot 3

1. Introduction

Standard 128‑bit random UUIDs are widely used but are poorly suited for database primary keys because they are large and cause low B‑tree index fill factors, frequent page splits, and excessive memory consumption.

Each UUID occupies 16 bytes, inflating foreign‑key columns.

B‑tree indexes store data in sorted order; random values break this order.

Random keys lead to low page fill factors and many page splits, wasting disk and buffer‑pool space.

2. TSID Overview

TSID (Time‑Sorted Identifier) is an open‑source Java library that generates unique IDs ordered by creation time. It combines concepts from Twitter's Snowflake and the ULID specification.

Time‑ordered generation

Fits in a 64‑bit long Can be represented as a 13‑character URL‑safe string (Crockford Base‑32)

Shorter than UUID, ULID, and KSUID

3. Maven Dependency

<dependency>
  <groupId>io.hypersistence</groupId>
  <artifactId>hypersistence-tsid</artifactId>
  <version>2.1.4</version>
</dependency>

Check the latest version on Maven Repository.

4. TSID Structure

TSID consists of two parts:

Time part (42 bits) : milliseconds elapsed since 2020‑01‑01 00:00:00 UTC.

Random part (22 bits) : composed of a node identifier (0‑20 bits) and a counter (2‑22 bits). The number of node bits determines the counter bits.

Example with default 10‑bit node and 12‑bit counter:

adjustable →
|------------------------------------------|----------|------------|
 time (42 bits)               node (10 bits)   counter (12 bits)

Ranges:

Time: 2^42 ≈ 69 years (signed 64‑bit) or ≈139 years (unsigned).

Node: up to 2^20‑1 = 1,048,575 (configurable 0‑20 bits).

Counter: up to 2^12‑1 = 4,095 per millisecond with the default node size.

5. Basic API Usage

Creating a TSID and converting it:

TSID tsid = TSID.Factory.getTsid();
long id = TSID.Factory.getTsid().toLong(); // 797263809025044592
String strId = TSID.Factory.getTsid().toString(); // 0P43KE5EXXM3Z

Batch generation:

for (int i = 0; i < 20; i++) {
    long id = TSID.Factory.getTsid().toLong();
    System.err.println(id);
}

Fast generation (up to 2^22 IDs per millisecond) using TSID.fast() which relies on AtomicInteger. Use the regular factory for cryptographically‑secure scenarios.

TSID tsid = TSID.fast();

Other utilities:

Parse from a 13‑character string: TSID tsid = TSID.from("0123456789ABC"); Retrieve creation timestamp: Instant instant = TSID.Factory.getTsid().getInstant(); Encode to base‑62: String base62 = TSID.Factory.getTsid().encode(62); Custom formatting:

String formatted = tsid.format("pack%S"); // pack0P43SMJ0FH80H

6. Custom JPA Primary‑Key Generation

Component that builds a configurable TSID.Factory:

@Component
public class TsidComponent {
    public static final String TSID_NODE_COUNT_PROPERTY = "tsid.node.count";
    public static final String TSID_NODE_COUNT_ENV = "TSID_NODE_COUNT";
    public static TSID.Factory TSID_FACTORY;

    static {
        String nodeCountSetting = System.getProperty(TSID_NODE_COUNT_PROPERTY);
        if (nodeCountSetting == null) {
            nodeCountSetting = System.getenv(TSID_NODE_COUNT_ENV);
        }
        int nodeCount = nodeCountSetting != null ? Integer.parseInt(nodeCountSetting) : 256;
        TSID_FACTORY = getTsidFactory(nodeCount, 0);
    }

    public static TSID.Factory getTsidFactory(int nodeCount, int nodeId) {
        int nodeBits = ((int) (Math.log(nodeCount) / Math.log(2))) + 1;
        return TSID.Factory.builder()
                .withRandomFunction(TSID.Factory.THREAD_LOCAL_RANDOM_FUNCTION)
                .withNodeBits(nodeBits)
                .withNode(nodeId)
                .build();
    }
}

Custom annotation for JPA:

@IdGeneratorType(PackIdGenerator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface PackSequence {}

Entity example:

@Entity
@Table(name = "o_student")
public class Student {
    @Id
    @PackSequence
    private Long id;
    private String name;
    private String sno;
}

7. Test Example

@Resource
private StudentService studentService;

@Test
public void testSave() {
    for (int i = 0; i < 10; i++) {
        Student student = new Student();
        student.setName("pack_xg_%s".formatted(i));
        student.setSno("X00002-%s".formatted(i));
        this.studentService.save(student);
    }
}

The TSID generator is thread‑safe, making it suitable for high‑concurrency scenarios such as logging or distributed systems.

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.

JavaSpring BootDistributed IDjpaUUID alternativeTSID
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.