WoGu
WG001ERRORDeterminism

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;
  }
}

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.