UUID.randomUUID() inside Workflow
- Engine
- Temporal Java SDK
- Since
- 0.1.0
- Auto Fix
- Not Available
- Category Range
- WG001–WG099
Description
A Temporal workflow implementation — or any code reachable from a workflow's entry
point, however many method calls away — calls java.util.UUID.randomUUID().
Why This Matters
Temporal workflows don't run once and stay in memory: they are replayed. Whenever a worker needs to reconstruct a workflow's state (after a restart, a worker handoff, or simply to advance past a completed activity), it re-executes the workflow's code from the beginning of the event history, feeding it the recorded results of everything that already happened.
For that replay to reconstruct the exact same state, the workflow code must be
deterministic: given the same history, it must make the same decisions and produce the
same values every time it runs. UUID.randomUUID() is seeded from system entropy, not from
workflow history — it returns a different value on every call, including during replay.
When a replay produces a different UUID than the original execution did, Temporal detects
that the code's behavior has diverged from the recorded history and raises a
NonDeterministicException, which can stall or fail the workflow.
This is exactly the same class of bug as calling Math.random(), Instant.now(), or
Thread.sleep() directly in workflow code — WG001 is the first of several rules WoGu will
add for this category (see numbering strategy).
Violation Example
public class PaymentWorkflowImpl implements PaymentWorkflow {
@Override
public String processPayment(String accountId) {
String transactionId = UUID.randomUUID().toString();
return "Payment " + transactionId + " processed for account " + accountId;
}
}This is flagged even when the call is several methods away from the workflow entry point:
public class PaymentWorkflowImpl implements PaymentWorkflow {
private final PaymentService paymentService = new PaymentService();
@Override
public String processPayment(String accountId) {
// WG001 still fires: WoGu's call-graph analysis follows this call into
// PaymentService.executeUUIDError() and finds UUID.randomUUID() inside it.
return paymentService.executeUUIDError();
}
}
class PaymentService {
String executeUUIDError() {
return UUID.randomUUID().toString();
}
}Compliant Example
import io.temporal.workflow.Workflow;
public class PaymentWorkflowImpl implements PaymentWorkflow {
@Override
public String processPayment(String accountId) {
String transactionId = Workflow.randomUUID().toString();
return "Payment " + transactionId + " processed for account " + accountId;
}
}Recommended Fix
Replace java.util.UUID.randomUUID() with io.temporal.workflow.Workflow.randomUUID().
Workflow.randomUUID() is backed by a random number generator seeded deterministically
from the workflow's run ID and current replay position, so it returns the same value on
every replay while still being effectively random across different workflow executions.
If you need randomness inside an Activity rather than workflow code, this rule doesn't
apply — activities are not replayed, so UUID.randomUUID() is safe there. WG001 only
flags code reachable from a workflow's own entry point (see
Call Graph Analysis below for what "reachable" means).
False Positives
WG001 uses a call-graph analysis: starting from a workflow's entry-point method(s) (the
implementation methods matching an @WorkflowMethod-annotated interface method), it
follows every method call that can be resolved to another method's source within the same
project, however many hops deep, looking for UUID.randomUUID() along the way.
This analysis intentionally stops, without reporting anything past that point, when it reaches a call it cannot resolve to source it can see — for example:
- A call into a compiled dependency (a jar) rather than the project's own source.
- A call resolved through Java reflection or dynamic dispatch that static analysis can't follow.
- An interface method with multiple possible implementations, where WoGu can't determine which one actually runs.
This means WG001 can produce false negatives (a real violation hidden behind a call it
can't see into) but is designed to avoid false positives — it will not flag a call
just because it looks superficially similar to UUID.randomUUID(); it must resolve to
that exact method, or match it directly by name and import when it's the workflow class's
own code. If you believe WG001 is flagging or missing something incorrectly, please open
an issue with a minimal reproduction.
Found an issue with this rule's detection? Open an issue with a minimal reproduction.