java.util.Random inside Workflow
- Engine
- Temporal Java SDK
- Since
- 0.3.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 — constructs a java.util.Random (new Random())
or draws a value from one (nextInt(), nextLong(), nextDouble(), nextBoolean()).
Why This Matters
An unseeded java.util.Random draws its values from a source that is not recorded in
workflow history. Temporal workflows are replayed: whenever a worker needs to
reconstruct a workflow's state, it re-executes the workflow's code from the beginning of
event history. Replay re-executes the construction (or the draw), producing a different
sequence of values than the original execution saw. If any decision in the workflow
depends on that value, replay diverges from the original execution and raises a
NonDeterministicException.
io.temporal.workflow.Workflow.newRandom() is the correct replacement: it returns a
Random seeded deterministically from workflow history, so it produces the same sequence
of values on every replay.
Violation Example
import java.util.Random;
public class PaymentWorkflowImpl implements PaymentWorkflow {
@Override
public void processPayment() {
Random random = new Random();
int discount = random.nextInt(10);
}
}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 void processPayment() {
// WG005 still fires: WoGu's call-graph analysis follows this call into
// PaymentService.pickDiscount() and finds `new Random()` inside it.
paymentService.pickDiscount();
}
}
class PaymentService {
int pickDiscount() {
return new Random().nextInt(10);
}
}Constructing the Random and calling an instance method on it are each their own
violation, so the example above is reported twice: once for new Random(), once for the
resolved Random.nextInt() call.
Compliant Example
import io.temporal.workflow.Workflow;
public class PaymentWorkflowImpl implements PaymentWorkflow {
@Override
public void processPayment() {
int discount = Workflow.newRandom().nextInt(10);
}
}Recommended Fix
Replace new Random() (and the draws from it) with Workflow.newRandom() (or another
Temporal-provided deterministic alternative). It is seeded from workflow history, so it
produces the same sequence of values on every replay.
If you need this class's exact algorithm outside of workflow determinism constraints (e.g. inside an Activity), this rule doesn't apply there — activities are not replayed. WG005 only flags code reachable from a workflow's own entry point, and stops before entering an Activity implementation (see False Positives).
False Positives
WG005 uses the same call-graph analysis as WG001–WG004, extended
to also look for matching constructor calls (new Random()) alongside matching method
calls, at every call site reachable from a workflow's entry-point method(s).
An instance call like random.nextInt() doesn't name Random anywhere at the call site,
so it can't be matched syntactically the way a static call can; WG005 instead resolves
the call via WoGu's symbol solver and checks that it declares to java.util.Random
itself. ThreadLocalRandom and other Random subclasses that override a method (see
WG006) resolve to their own declaring class, not java.util.Random, and are
correctly not flagged by this rule.
As with the other determinism rules, this analysis intentionally stops, without
reporting anything past that point, at two kinds of boundary: a call it cannot resolve to
source it can see, and a call that resolves into a Temporal Activity implementation
(recognized by its @ActivityInterface/@ActivityMethod annotations, or by resolving
only as far as the activity interface's bodyless method) — activity code is not replayed,
so it is exempt from this rule by design.
This means WG005 can produce false negatives (a real violation hidden behind a call it can't see into) but is designed to avoid false positives. If you believe WG005 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.