UUIDv8 is a UUID type defined by RFC 9562 that allows for customization.
Our custom UUIDs are generated by UUIDv8.generate()
using 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 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 Two 2 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());
}
}
}
System.out.println(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.System.out.println(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
Note that, in the last examples, the 2 digits "8" among the substr()
and overlay()
functions. This simplification is possible due to the two bits set to zero in the Java method.
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;
* `example.com` is the 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);
}
}
}
In summary, you just generate a hash from the Tag URI, take 16 bytes in hexadecimal, and then replace two digits "8" in specific positions.