Random Number Generator in Java (Math.random, Random, ThreadLocalRandom)
Java offers four built-in ways to generate random numbers: Math.random(), java.util.Random, ThreadLocalRandom, and SecureRandom. Each has a specific role; picking the wrong one leads to bugs or performance problems.
Math.random() β the quickest way
double r = Math.random(); // 0.0 (inclusive) to 1.0 (exclusive)
Good enough for prototypes and simple randomness. Returns a double; convert to an integer range with a bit of math:
// Random integer in [0, 100)
int n = (int) (Math.random() * 100);
// Random integer in [min, max] (inclusive)
public static int rand(int min, int max) {
return min + (int) (Math.random() * (max - min + 1));
}
Under the hood, Math.random delegates to a shared Random instance β fine for scripts, but contended across threads.
java.util.Random β richer API
import java.util.Random;
Random rnd = new Random();
int i = rnd.nextInt(); // any int, positive or negative
int b = rnd.nextInt(100); // [0, 100)
int c = rnd.nextInt(10, 20); // [10, 20) β Java 17+
long l = rnd.nextLong();
double d = rnd.nextDouble(); // [0.0, 1.0)
float f = rnd.nextFloat(); // [0.0, 1.0)
boolean z = rnd.nextBoolean();
byte[] bytes = new byte[16];
rnd.nextBytes(bytes); // random bytes
Seeded β reproducible sequences
Random rnd = new Random(42);
// Same seed β same sequence every run. Essential for tests, simulations, games.
Stream API (Java 8+)
int[] ten = rnd.ints(10, 0, 100).toArray(); // 10 random ints in [0, 100)
rnd.ints(5).forEach(System.out::println); // 5 arbitrary ints
ThreadLocalRandom β fast and thread-safe
For multithreaded code, ThreadLocalRandom gives each thread its own instance β no contention, no synchronization overhead.
import java.util.concurrent.ThreadLocalRandom;
int n = ThreadLocalRandom.current().nextInt(0, 100);
double d = ThreadLocalRandom.current().nextDouble(1.0, 2.0);
Prefer this over Math.random or a shared Random in any multithreaded application.
SecureRandom β cryptographic
For security-sensitive randomness (tokens, session IDs, encryption keys), only SecureRandom provides the unpredictability required. It's slower, but properly seeded from OS entropy.
import java.security.SecureRandom;
SecureRandom sr = new SecureRandom();
byte[] token = new byte[32];
sr.nextBytes(token);
// Encode as hex string
String hex = java.util.HexFormat.of().formatHex(token); // Java 17+
Never use Math.random or Random for passwords, session tokens, or cryptographic keys.
Choosing the right generator
| Use case | Generator |
|---|---|
| Quick script or demo | Math.random() |
| Single-threaded app, seeded for reproducibility | new Random(seed) |
| Multi-threaded app | ThreadLocalRandom.current() |
| Session tokens, passwords, keys | SecureRandom |
| Any new code | RandomGenerator (Java 17+) |
RandomGenerator (Java 17+)
Java 17 introduced the RandomGenerator interface and a factory of improved algorithms (L64X128MixRandom, L128X256MixRandom, etc.):
import java.util.random.RandomGenerator;
RandomGenerator g = RandomGenerator.of("L64X128MixRandom");
int x = g.nextInt(100);
Better statistical properties and performance than the legacy Random.
Common patterns
Random element from a list
List<String> names = List.of("Alice", "Bob", "Carol");
String pick = names.get(ThreadLocalRandom.current().nextInt(names.size()));
Shuffle a list
List<Integer> deck = new ArrayList<>(List.of(1,2,3,4,5,6,7,8,9,10));
Collections.shuffle(deck);
// Or with a seeded Random for reproducibility:
Collections.shuffle(deck, new Random(42));
Random in a range [min, max] inclusive
int n = ThreadLocalRandom.current().nextInt(min, max + 1);
Weighted random choice
// Weights: A=3, B=1, C=2 β total = 6
int roll = ThreadLocalRandom.current().nextInt(6);
String pick = roll < 3 ? "A" : roll < 4 ? "B" : "C";
Common mistakes
- Creating a new
Random()inside a loop β all instances seeded with the current millisecond β similar or identical sequences. - Using
Math.randomin multithreaded code β contention, unnecessary overhead. - Using non-secure randomness for tokens.
- Forgetting that
nextInt(100)returns [0, 100), not [1, 100].
For most business code, ThreadLocalRandom.current().nextInt(min, max) is the right default. For anything security-related, always SecureRandom.