# TSID Creator: time-sortable 64-bit identifiers for Java primary keys

> tsid-creator is an MIT-licensed Java library that generates Time-Sorted Unique Identifiers, packing a 42-bit millisecond timestamp and 22 random bits into a value that fits in a long and encodes to a 13-character Crockford base32 string. It suits a Spring or JPA project that wants ordered primary keys, and its own maintainer has put it in maintenance mode.

**f4b6a3/tsid-creator** — A Java library for generating Time-Sorted Unique Identifiers (TSID).

- Repository: https://github.com/f4b6a3/tsid-creator
- Stars: 559 · Forks: 58
- Language: Java
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/f4b6a3-tsid-creator

## What a TSID is, and where the design comes from

tsid-creator is a Java library for generating Time-Sorted Unique Identifiers. It brings together ideas from Twitter's Snowflake and the ULID specification.

The properties the project lists are the whole pitch. The identifiers are sorted by generation time. They can be stored as an integer of 64 bits, and they can be stored as a string of 13 characters. The string format is encoded to Crockford's base32, which makes it URL safe, case insensitive and free of hyphens. And it is shorter than UUID, ULID and KSUID.

That combination is the reason the library exists. A UUID is 16 bytes and random, so it scatters index inserts across the whole key space. A plain auto-increment integer is compact and ordered but is a coordination problem across shards and it leaks information about volume. A TSID is a middle position: ordered like a timestamp, compact like an integer, and unique like a random one.

The idea of splitting a 64-bit value into a timestamp and a random part is what Twitter's Snowflake did, and the idea of a lexicographically sortable text encoding is what ULID standardised. This library takes both and combines them in a way that a Java service can use directly.

The project is MIT licensed, the primary language is Java, and the releases are tsid-creator-5.2.6 from 2024-01-03, tsid-creator-5.2.5 from 2023-09-03 and tsid-creator-5.2.4 from 2023-05-04, with the last push to the repository on 2026-06-14. The repository carries a pom.xml, the source under src/, a benchmark/ directory, a docs/ directory and a CHANGELOG.md.

One note sits at the top of the documentation and it is worth reading before anything else. The software is in maintenance mode because its maintainer believes there is nothing to change or add. That is an unusual and honest statement, and it changes how you should evaluate a dependency: the code is finished rather than stalled, and you should not expect new features.

## The API, and the two ways to consume the value

The usage surface is three calls, and that is the whole API.

```java
Tsid tsid = TsidCreator.getTsid();
```

From there you can unwrap the value as a long:

```java
long number = TsidCreator.getTsid().toLong(); // 38352658567418872
```

or take the string form:

```java
String string = TsidCreator.getTsid().toString(); // 01226N0640J7Q
```

The generator is thread-safe, which is the property that matters if you are generating identifiers from a request thread, and the toLong method simply unwraps the internal long value of a TSID.

The two representations are not interchangeable, and the difference shows up in the generated sequences. As longs, consecutive identifiers differ by one, and the jump when the millisecond changes is visible:

```text
38352658567418867
38352658567418868
38352658567418869
38352658567418870
38352658567418871
38352658567418872
38352658567418873
38352658567418874
38352658573940759 < millisecond changed
```

As strings, the same sequence appears in Crockford base32, and the structure is still visible in the text form, with the time part on the left and the random part on the right:

```text
01226N0640J7K
01226N0640J7M
01226N0640J7N
01226N0640J7P
01226N0640J7Q
01226N0640J7R
01226N0640J7S
01226N0640J7T
01226N0693HDA < millisecond changed
```

The string form has a practical use that the documentation spells out: it is useful for languages that store numbers in double-precision 64-bit binary format IEEE 754, such as JavaScript, where a long cannot be represented exactly and a numeric identifier would be silently rounded. The text form sidesteps the problem entirely, and the 13 characters are short enough to put in a URL without thinking about it.

## The bit layout, and where the throughput ceiling comes from

A TSID has two components, and the layout is the design.

The time component is 42 bits, and it is the count of milliseconds since 2020-01-01 00:00:00 UTC. The random component is 22 bits, and it has two sub-parts: a node ID of 0 to 20 bits, and a counter of 2 to 22 bits. The counter bits depend on the node bits.

The default is 10 node bits and 12 counter bits:

```
|------------------------------------------|----------|------------|
       time (msecs since 2020-01-01)           node       counter
                42 bits                       10 bits     12 bits
```

which gives a maximum node value of 1023 and a maximum counter value of 4095, so the maximum number of TSIDs that can be generated per millisecond is 4096. The node bits are adjustable from 0 to 20, and adjusting them changes the counter size, so it is a straight trade between how many machines you have and how many identifiers each machine can issue in a millisecond.

The time component has a long horizon. Two to the power of 42 is about 69 years when stored in a signed 64 bits integer field, and about 139 years in an unsigned one. Since Java's long is signed, a database column declared BIGINT gets the shorter horizon, and that is a decision to make deliberately rather than by accident.

One more behaviour is worth knowing, and it is the kind of detail that only shows up under load. The time component can be 1 millisecond or more ahead of the system time when necessary to maintain monotonicity and generation speed. So a TSID is a logical clock, not a reading of the wall clock, and if the system clock jumps backwards the identifiers keep increasing.

That is what makes the sort order reliable, and it is also the reason the identifiers are not a trustworthy timestamp of when something happened.

## Nodes, Maven coordinates, and where to look next

The node identifier is the collision avoidance mechanism, and the documentation is clear about what it has to be.

A simple way to avoid collisions is to make sure that each generator has its exclusive node ID. A node, as the library calls it, can be a physical machine, a virtual machine, a container, a k8s pod, a running process, a database instance number, and so on. The point is that the node is a unit that runs exactly one generator, and the uniqueness of the identifiers depends on the node identifiers not overlapping.

That is the whole operational contract. A single JVM has no problem. A cluster of services that each generate identifiers needs a scheme for handing out node identifiers, and if two services end up with the same node, their identifiers can collide within the same millisecond. The library gives you the mechanism, and the deployment has to give you the uniqueness.

The dependency is added to a pom.xml as:

```xml
<dependency>
  <groupId>com.github.f4b6a3</groupId>
  <artifactId>tsid-creator</artifactId>
  <version>5.2.6</version>
</dependency>
```

and the jar can be downloaded directly from maven.org.

The project also states its modularity explicitly. The module and bundle names are the same as the root package name, the Java module name is com.github.f4b6a3.tsid and the OSGi symbolic name is com.github.f4b6a3.tsid. That matters for anyone running the library on a modular Java runtime or in an OSGi container, where a library that does not declare a module name is a nuisance to consume.

The project links a recommended reading list, and two entries are worth picking out for an engineer deciding whether to use this. There is a link to how not to use TSID factories, which is a warning written by someone who has seen the pattern misused, and a link to the best way to generate a TSID entity identifier with JPA and Hibernate, which points at a different library.

That last point is the one to note. If you want the identifiers to be managed for you by your persistence layer, with the entity identifier set and the column type handled, the project itself points you at hypersistence-tsid by vladmihalcea, and describes its own hint as being for readers who do not like the design of this API. tsid-creator is the generator; the Hibernate integration is a separate concern with a separate dependency.

## Where TSID Creator is the wrong tool

The first limit is the throughput ceiling, and it is worth calculating rather than assuming. With the default 10 node bits, one generator can issue at most 4096 identifiers per millisecond, which is about 4 million per second in the best case. That is a lot for a business application and not enough for a high-traffic write path, and the fix is to give up node bits for counter bits, which means giving up the number of machines you can run, and that is a real architectural constraint rather than a tuning parameter.

The second is that the identifiers are not sortable by true creation time. The documentation says the time component can be 1 millisecond or more ahead of the system time when necessary to maintain monotonicity and generation speed. So a TSID tells you when the generator thought it was, not when the row was committed, and a database default or a created_at column with microsecond precision is more accurate than the identifier for ordering by real time.

The third is the security model. The random component is 22 bits, split between node and counter, and the node part is often derived from a machine or instance number. That is not a secret, and it is not meant to be. A TSID is a guessable, sequential, low-entropy identifier, which is exactly what you want for a primary key and exactly what you do not want for a session token, a password reset link or anything else an attacker can enumerate.

The fourth is the maintenance mode. The project states plainly that it is in maintenance mode because the maintainer believes there is nothing to change or add. The last release is from January 2024, and the last push to the repository was on 2026-06-14. For a library whose format is frozen, that is a reasonable position, and the format will not change under you. For a library you depend on for correctness of identifier ordering, it means the bus factor is one person who has said they are done.

And the fifth is what the library is not. It generates identifiers. It does not manage them for a persistence layer, it does not allocate node identifiers across a cluster, it does not give you a database sequence, and it does not handle the case where two services were both given node zero. The feature list in the documentation is a generator with a factory, and the surrounding problems, which are the ones that actually bite, are yours.

## Conclusion

TSID Creator is worth adding to a Java project that wants primary keys that sort by creation time, because the layout puts the timestamp in the high bits so an index insert is an append, because the value still fits in a signed long so the column stays a normal integer type, and because the string form is URL safe and case insensitive for the places where you have to pass an identifier as text. It is the wrong tool if you need cryptographic randomness, and it is the wrong tool if you want a JPA integration that manages entity identifiers for you. Before you adopt it, check three things: that you accept the throughput ceiling, because with the default 10 node bits and 12 counter bits a single generator can produce at most 4096 identifiers per millisecond, so a high-traffic service needs either more nodes or a different scheme; that you understand the maintenance note, since the project states it is in maintenance mode because its maintainer believes there is nothing to change or add, and the last release is tsid-creator-5.2.6 from 2024-01-03; and that you read the node identifier section, because the node is what stops two generators colliding, and a node can be a physical machine, a virtual machine, a container, a Kubernetes pod, a running process or a database instance number, which means the uniqueness guarantee is only as good as your node assignment.

## FAQ

### How do I add TSID Creator to a Maven project?

Add the dependency to your pom.xml with groupId com.github.f4b6a3, artifactId tsid-creator and version 5.2.6, and the jar is also downloadable directly from maven.org. The library declares a Java module name and an OSGi symbolic name of com.github.f4b6a3.tsid.

### What is the structure of a TSID?

A TSID has a 42-bit time component counting milliseconds since 2020-01-01 00:00:00 UTC, and a 22-bit random component split into a node ID of 0 to 20 bits and a counter of 2 to 22 bits. With the default 10 node bits there are 12 counter bits, so there are 1024 possible nodes and at most 4096 identifiers per millisecond per node.

### How do I create a TSID and convert it to a long or a string?

Call TsidCreator.getTsid() to get a Tsid, then use toLong() to unwrap the 64-bit value or toString() to get the 13-character Crockford base32 form. The generator is thread-safe, and the string form is useful for languages that store numbers as IEEE 754 doubles, such as JavaScript, where a long would be rounded.

## Sources

- [f4b6a3/tsid-creator on GitHub](https://github.com/f4b6a3/tsid-creator)
- [Issues](https://github.com/f4b6a3/tsid-creator/issues)
- [License: MIT](https://github.com/f4b6a3/tsid-creator/blob/master/LICENSE)
- [README](https://github.com/f4b6a3/tsid-creator/blob/master/README.md)
- [Releases](https://github.com/f4b6a3/tsid-creator/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/f4b6a3-tsid-creator
