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 caseGenerator
Quick script or demoMath.random()
Single-threaded app, seeded for reproducibilitynew Random(seed)
Multi-threaded appThreadLocalRandom.current()
Session tokens, passwords, keysSecureRandom
Any new codeRandomGenerator (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.random in 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.