This document defines two unique identifiers:
- a custom UUID based on RFC 9562;
- a custom Tag URI based on RFC 4151.
UUIDv8 is a UUID type defined by RFC 9562 that allows for customization.
Our custom UUID is generated by UUIDv8.generate(), which uses a SHA-256 hash algorithm.
SHA-256 was chosen because it is natively available on Java 7+ and PostgreSQL 11+.
Additionally, two bits are left unset to make them reserved, also to make it easier to implement our UUID in other languages.
This example is a util class that generates UUIDv8 based on SHA-256:
public class UUIDv8 {
/**
* Generate a SHA-256-based UUIDv8.
* <p>
* No namespace is required like in UUIDv5. Also 2 bits are reserved unset so that
* the generation is a lot easier in other languages by using substring functions.
* <p>
*
* @param a string
* @return a UUID
*/
public static UUID generate(String str) {
final String algoritmo = "SHA-256";
final MessageDigest digest = digest(algoritmo);
// Calculate hash from which 16 bytes will be used
digest.update(str.getBytes(StandardCharsets.UTF_8));
ByteBuffer hash = ByteBuffer.wrap(digest.digest());
// Format the 16 bytes from the hash according to RFC 9562 and reserve 2 bits
hash.put(6, (byte) (hash.get(6) & 0x0f | 0x80)); // inject the 4 version bits
hash.put(8, (byte) (hash.get(8) & 0x3f | 0x80)); // inject the 2 variant bits
hash.put(8, (byte) (hash.get(8) & 0b1100_1111)); // unset 2 bits to reserve them
return new UUID(hash.getLong(), hash.getLong());
}
private static MessageDigest digest(String algorithm) {
try {
return MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
}UUIDv8.generate("tag:example.com,2024:application:instance:entity:123");745e831a-9f04-8dda-884a-5d62bce8fc9c
💡 HINT
The fact that the our method doesn't expect a namespace (like UUIDv5 does) doesn't mean you can't use one. For example, you can put a private prefix in your input string, just like a salt.UUIDv8.generate("MY_PRIVATE_PREFIX" + "tag:example.com,2024:application:instance:entity:123");22f6a9b4-637a-81ce-81f6-6efc8b74c6f8
This example shows how to generate this UUIDv8 on Bash:
echo -n "tag:example.com,2024:application:instance:entity:123" | sha256sum \
| awk '{ print substr($0,1,12) "8" substr($0,14,3) "8" substr($0,18,15) }'745e831a9f048dda884a5d62bce8fc9c
This is another example but now with PostgreSQL's SQL:
select overlay (
overlay (
substring(
encode(
sha256('tag:example.com,2024:application:instance:entity:123')
, 'hex')
, 1, 32)
placing '8' from 13 for 1)
placing '8' from 17 for 1);745e831a9f048dda884a5d62bce8fc9c
RFC 4151 specifies the 'tag' URI scheme.
A Tag URI is a kind of unique ID. It looks like a URL, but it doesn't say how to find (locate) a resource; it only identifies the resource.
💡 HINT
Read https://taguri.org/
We are using this Tag URI format (without spaces):
tag : example.com , 2024 : application : instance : entity : 123
where:
* `tag` is the scheme name, which is fixed;
* `example.com` is our domain name;
* `2024` is an year we own the domain name;
* `application` is the application name;
* `instance` is the application instance name;
* `entity` is a persistence entity name;
* `123` is the ID of a persistence entity.
The entity below shows how to use our UUIDv8 with Tag URI in a JPA Entity:
@Entity
public class MyEntity {
@Id
@Column(name = "id")
private UUID id;
@Column(name = "tag")
@Pattern(regexp = "^tag:\\w+,\\w+:\\w+:\\w+:\\w+:\\w+$")
private String tag;
public MyEntity(String tag) {
this.tag = tag;
}
@PrePersist
public void prePersist() {
if (id == null) {
id = UUIDv8.generate(tag);
}
}
}