Event-driven systems rely on messages to coordinate actions across services. Each event must have a unique identifier so it can be stored uniquely, tracked and correlated to other events. The most common choice to generate these ids is to use UUIDv4, which are random numbers. While working on event-driven systems, I have found a few instances where UUIDv5 (named UUIDs) might be a better choice.
- Database lookups – If an event is generated from a known entity (e.g., session ID, username), you still need to store the event and look it up from a database if you need to use it later in the flow.
- Unpredictable tests – Random IDs make integration and unit testing harder because test assertions can’t rely on fixed values.
In this post, I’ll show how UUIDv5 (which is a deterministic, namespace-based UUID) can make event handling faster, more predictable, and easier to test.
Example: an e-commerce event flow
Let’s use a (simplified) e-commerce system as an example. The user can buy items using a web browser or from a mobile app. This will start the yellow process steps in the picture. Below the process steps are thye blue microservices which executes all the logic. And, at the bottom is the messaging infrastructure that interacts with all the microservices.

During the purchase journey, events such as CheckoutStarted, OrderCreated, PaymentProcessed, and ItemsShipped are emitted. These events are used for several purposes, the first is to move the flow forward. One event causes the business logic to execute the next step in the process. These events will also be used by an analytics team to see which products are sold, how many carts are abandoned etc.
When the user has received all the items, we should be able to reconstruct the customer journey based on the events emitted during the journey. Then we realize that there are relations between the events. These relations are usually implicit and not documented (because they are obvoius, right?). In the picture below, I have drawn some of the implicit relations.

The events happen in the context of a customer journey. All events should know in which context they being emitted in. The business logic should know why an event is emitted. There must be a reason (or a cause) for emitting an ItemsShipped event. If the business logic wants to add these explicit relations, it needs to get the eventIds from somewhere. There is a need for storing all the events in a database to be able to look them up later in the flow.
The events drawn in the picture have different scope. For instance, the CustomerJourneyStarted event will span over all other events. The user object, which is probably store in a user database with a user ID (a UUID), lives across several customer journeys.
The problem with random event IDs
If every event ID is a random number, it is not possible to derive the ID from known data, we need to store them in a database so they can be retrieved later in the flow. For example, many web platforms create an integer based session id. It can be used to create an event ID for the CustomerJourneyStarted event. Or, the combination username+sessionId.
By generating UUID from known data, all the microservices can generate the correct UUIDs instead of looking them up in a database, which comes with a performance penalty.
Another problem I have experienced with random UUIDs is that testing of the system can be tricky, especially integration tests. If all components generate random IDs, it is not possible to know what ID is expected in a test and you can only assert that a ID has been generated.
Use UUIDv5 for deterministic IDs
By using UUIDv5 for the events that have a larger scope makes these UUID deterministic and repeatable inside different components. This is a way to decrease the load on the lookup database and at the same time generate system wide unique IDs.
If we need to resend or replay a scenario, the system will generate the same IDs for events with bigger scope.
And, testing of the different components will get easier because we have better control over the expected results.
How UUIDv5 works
A UUIDv5 is created based on 2 parts, a namespace and a name of the object/event you want to generate the id for. The namespace should be a UUID that represents the whole system you are running. In our case, it is the whole e-commerce system. The namespace UUID should be generated once and then used by all components generating name-based UUIDs. (Let’s say that the namespace UUID for the e-commerce system is 5fe149e6-ce57-4a23-acb7-b77fd2faae96.)
The second part should be the ‘name’ of object/event. The word ‘name’ should be interpreted as ‘how you can name the object/event’. It is important to have a decided naming convention, so all components know how to name an object/event. For instance, a username can be named users/<username> and a customer journey can be journey/web/<int based session id>.
To generate a UUIDv5 you just need to put them together UUID(namespace, name) and calculate a SHA1 hash based on the concatenated string (Well, there is a little bit more to it). In our example, that would be UUID("“5fe149e6-ce57-4a23-acb7-b77fd2faae96", “users/<username>).
There is no UUIDv5 implementation in Java’s standard library (Java 24, latest at the time of writing). There is however a function for generating name-based UUIDs using UUIDv3, but as the RFC states:
Where possible, UUIDv5 SHOULD be used in lieu of UUIDv3. For more information on MD5 security considerations, see [RFC6151].
I decided to make my own implementation of UUIDv5 in Java.
Java implementation of a UUIDv5 generator
The RFC defines four standard namespaces, where DNS (namespace id: 6ba7b810-9dad-11d1-80b4-00c04fd430c8) is one of them. See the RFC9562 for the other ones.
My first test is based on the example in the Appendix of RFC 9562. I want to create a UUID generator with a namespace id (this part will be fixed for all generations of UUIDs) and then send in a known name to the generator.
public class RFC9562Tests {
@Test
public void chapter_A_4() {
String uuidName = "www.example.com";
UUIDv5Generator generator = new UUIDv5Generator("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
UUID namedUuid = generator.fromName(uuidName);
String actualUuid = namedUuid.toString();
assertEquals("2ed6657d-e927-568b-95e1-2665a8aea6a2", actualUuid);
assertEquals(5, namedUuid.version());
assertEquals(2, namedUuid.variant());
}
}
And the code looks like this:
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
public class UUIDv5Generator {
private final byte[] namespaceBytes;
/* Code to define the standard namespaces. Not interesting for this blog post */
public UUIDv5Generator(String namespaceUuid) {
this(UUID.fromString(namespaceUuid));
}
public UUIDv5Generator(UUID namespaceUuid) {
ByteBuffer bb = ByteBuffer.allocate(16);
bb.putLong(namespaceUuid.getMostSignificantBits());
bb.putLong(namespaceUuid.getLeastSignificantBits());
namespaceBytes = bb.array();
}
public UUID fromName(String name) {
byte[] uuidNameBytes = name.getBytes(StandardCharsets.UTF_8);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Can not get SHA-1 digest instance", e);
}
digest.update(namespaceBytes);
digest.update(uuidNameBytes);
byte[] digestBytes = digest.digest();
// Set version and variant, RFC 9562 chapter 5.5
digestBytes[6] &= 0x0f;
digestBytes[6] |= 0x50;
digestBytes[8] &= 0x3f;
digestBytes[8] |= (byte)0x80;
ByteBuffer bb = ByteBuffer.wrap(digestBytes);
long msb = bb.getLong();
long lsb = bb.getLong();
// Rest of the SHA-1 digest is ignored
return new UUID(msb, lsb);
}
}
I also have a test for generating UUIDv5 in a custom namespace.
public class CustomNamespaceTests {
private static final String CUSTOM_NAMESPACE_UUID = "e2d304b7-67dd-480b-83e3-68484a11e84f";
@Test
public void custom_event_and_attribute() {
// uuidgen --namespace e2d304b7-67dd-480b-83e3-68484a11e84f --name "event-1/attribute-2" --sha1
String uuidName = "event-1/attribute-2";
UUIDv5Generator generator = new UUIDv5Generator(CUSTOM_NAMESPACE_UUID);
UUID namedUuid = generator.fromName(uuidName);
String actualUuid = namedUuid.toString();
assertEquals("94bcbe7b-f3cb-505f-ad9e-1b31c89274f7", actualUuid);
assertEquals(5, namedUuid.version());
assertEquals(2, namedUuid.variant());
}
@Test
public void custom_namespace_invalid_uuid() {
assertThrows(IllegalArgumentException.class, ()
-> new UUIDv5Generator("invalid-uuid"));
}
}
That’s it a long description for less than 20 lines of code.
Summary
I don’t think UUIDv4 (random) should be used for all objects/events in an event-driven system. Events with a bigger scope should instead use UUIDv5. This way there is not need to lookup the event IDs from an external database and the load on this database will decrease.
It will also be easier to create tests due to the fact the the output of some components will have predictable results.
References
RFC 9562 – Universally Unique IDentifiers (UUIDs)
FAQ
Q: Why don’t you publish this as a library and push it to maven’s central repository?
A: I think this is so little code and it is not worth my time to manage a micro library like this. It is better for you to copy the code into your project.
