
Java Random – Generating Random Numbers
Java’s random number generation capabilities are essential for everything from testing and simulations to cryptographic applications and game development. Whether you’re building a load balancer that needs to distribute traffic randomly, implementing a cache eviction strategy, or creating secure tokens for API authentication, understanding Java’s random number generators will save you from subtle bugs and performance issues. This guide walks through the Random class, SecureRandom for cryptographic use cases, ThreadLocalRandom for concurrent applications, and covers the gotchas that can bite you in production environments.
How Java Random Number Generation Works
Java provides several classes for generating random numbers, each with different characteristics and use cases. The basic java.util.Random
class uses a linear congruential generator (LCG) algorithm, which is fast but not cryptographically secure. Under the hood, it maintains an internal seed value that gets updated with each call to generate the next pseudorandom number.
The algorithm follows this pattern:
next_seed = (seed * multiplier + increment) % modulus
This means if you know the seed and the algorithm parameters, you can predict the entire sequence. That’s why Random is fine for simulations and testing but terrible for security-sensitive applications.
For concurrent applications, Java 7 introduced ThreadLocalRandom
, which maintains separate generator instances per thread to avoid contention. For cryptographic purposes, SecureRandom
uses entropy sources from the operating system to generate unpredictable numbers.
Step-by-Step Implementation Guide
Basic Random Usage
Here’s how to get started with the standard Random class:
import java.util.Random;
public class BasicRandomExample {
public static void main(String[] args) {
// Create with system-generated seed
Random random = new Random();
// Or create with specific seed for reproducible results
Random seededRandom = new Random(12345L);
// Generate different types of random numbers
int randomInt = random.nextInt(); // Any int value
int boundedInt = random.nextInt(100); // 0 to 99
long randomLong = random.nextLong(); // Any long value
double randomDouble = random.nextDouble(); // 0.0 to 1.0
boolean randomBoolean = random.nextBoolean(); // true or false
// Generate random numbers in a range
int min = 10, max = 50;
int rangeInt = random.nextInt(max - min) + min; // 10 to 49
System.out.println("Random int: " + randomInt);
System.out.println("Bounded int: " + boundedInt);
System.out.println("Range int: " + rangeInt);
}
}
ThreadLocalRandom for Concurrent Applications
When you’re working in multithreaded environments, ThreadLocalRandom eliminates synchronization overhead:
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;
public class ConcurrentRandomExample {
public static void main(String[] args) {
// Generate random numbers in parallel streams
IntStream.range(0, 1000000)
.parallel()
.forEach(i -> {
// Each thread gets its own Random instance
int random = ThreadLocalRandom.current().nextInt(1, 101);
// Process the random number
processValue(random);
});
// Generate ranges more elegantly
int randomInRange = ThreadLocalRandom.current().nextInt(10, 50);
double randomDouble = ThreadLocalRandom.current().nextDouble(0.0, 1.0);
long randomLong = ThreadLocalRandom.current().nextLong(1000L, 9999L);
}
private static void processValue(int value) {
// Your processing logic here
}
}
SecureRandom for Cryptographic Applications
For generating secure tokens, session IDs, or cryptographic keys, use SecureRandom:
import java.security.SecureRandom;
import java.util.Base64;
public class SecureRandomExample {
private static final SecureRandom secureRandom = new SecureRandom();
public static String generateSessionToken() {
byte[] randomBytes = new byte[32];
secureRandom.nextBytes(randomBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
}
public static String generateApiKey() {
byte[] keyBytes = new byte[24];
secureRandom.nextBytes(keyBytes);
return Base64.getEncoder().encodeToString(keyBytes);
}
public static void main(String[] args) {
System.out.println("Session token: " + generateSessionToken());
System.out.println("API key: " + generateApiKey());
// Generate secure random numbers
int secureInt = secureRandom.nextInt(1000000);
System.out.println("Secure random int: " + secureInt);
}
}
Real-World Examples and Use Cases
Load Balancing with Weighted Random Selection
Here’s a practical example of using random numbers for server load balancing:
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class WeightedLoadBalancer {
private final List<Server> servers;
private final int totalWeight;
public WeightedLoadBalancer(List<Server> servers) {
this.servers = new ArrayList<>(servers);
this.totalWeight = servers.stream().mapToInt(Server::getWeight).sum();
}
public Server selectServer() {
int randomWeight = ThreadLocalRandom.current().nextInt(totalWeight);
int currentWeight = 0;
for (Server server : servers) {
currentWeight += server.getWeight();
if (randomWeight < currentWeight) {
return server;
}
}
return servers.get(servers.size() - 1); // Fallback
}
static class Server {
private final String name;
private final int weight;
public Server(String name, int weight) {
this.name = name;
this.weight = weight;
}
public int getWeight() { return weight; }
public String getName() { return name; }
}
public static void main(String[] args) {
List<Server> servers = Arrays.asList(
new Server("server-1", 30),
new Server("server-2", 50),
new Server("server-3", 20)
);
WeightedLoadBalancer balancer = new WeightedLoadBalancer(servers);
// Test distribution
Map<String, Integer> distribution = new HashMap<>();
for (int i = 0; i < 10000; i++) {
Server selected = balancer.selectServer();
distribution.merge(selected.getName(), 1, Integer::sum);
}
distribution.forEach((server, count) ->
System.out.println(server + ": " + count + " selections"));
}
}
Cache Eviction with Random Sampling
Random sampling is useful for implementing cache eviction policies:
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
public class RandomSamplingCache<K, V> {
private final Map<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();
private final int maxSize;
private final int sampleSize;
public RandomSamplingCache(int maxSize, int sampleSize) {
this.maxSize = maxSize;
this.sampleSize = sampleSize;
}
public void put(K key, V value) {
if (cache.size() >= maxSize) {
evictRandomSample();
}
cache.put(key, new CacheEntry<>(value, System.currentTimeMillis()));
}
private void evictRandomSample() {
List<K> keys = new ArrayList<>(cache.keySet());
Collections.shuffle(keys, ThreadLocalRandom.current());
// Find oldest entry in random sample
K oldestKey = keys.stream()
.limit(sampleSize)
.min(Comparator.comparing(k -> cache.get(k).timestamp))
.orElse(keys.get(0));
cache.remove(oldestKey);
}
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
return entry != null ? entry.value : null;
}
private static class CacheEntry<V> {
final V value;
final long timestamp;
CacheEntry(V value, long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
}
}
Performance Comparison and Benchmarks
Different random number generators have significantly different performance characteristics:
Generator | Single Thread (ops/sec) | Multi Thread (ops/sec) | Memory Usage | Use Case |
---|---|---|---|---|
Random | ~50M | ~8M (contended) | Low | Simple applications, testing |
ThreadLocalRandom | ~55M | ~200M (scales) | Medium | Concurrent applications |
SecureRandom | ~500K | ~2M | Low | Cryptographic applications |
SplittableRandom | ~60M | ~250M | Medium | Parallel streams, fork-join |
Here’s a benchmarking example you can run on your VPS or dedicated server:
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class RandomPerformanceBenchmark {
private static final int ITERATIONS = 10_000_000;
public static void main(String[] args) {
benchmarkRandom();
benchmarkThreadLocalRandom();
benchmarkSecureRandom();
}
private static void benchmarkRandom() {
Random random = new Random();
long start = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
random.nextInt(1000);
}
long duration = System.nanoTime() - start;
System.out.printf("Random: %.2f M ops/sec%n",
ITERATIONS / (duration / 1_000_000_000.0) / 1_000_000);
}
private static void benchmarkThreadLocalRandom() {
long start = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
ThreadLocalRandom.current().nextInt(1000);
}
long duration = System.nanoTime() - start;
System.out.printf("ThreadLocalRandom: %.2f M ops/sec%n",
ITERATIONS / (duration / 1_000_000_000.0) / 1_000_000);
}
private static void benchmarkSecureRandom() {
SecureRandom secureRandom = new SecureRandom();
long start = System.nanoTime();
for (int i = 0; i < ITERATIONS / 100; i++) { // Fewer iterations
secureRandom.nextInt(1000);
}
long duration = System.nanoTime() - start;
System.out.printf("SecureRandom: %.2f K ops/sec%n",
(ITERATIONS / 100) / (duration / 1_000_000_000.0) / 1_000);
}
}
Common Pitfalls and Best Practices
Avoid These Common Mistakes
- Creating new Random instances frequently: This is expensive and can lead to poor randomness if instances are created close in time with similar seeds
- Using Random in multithreaded code: The synchronization overhead kills performance and can create contention hotspots
- Using Random for security purposes: It’s predictable if an attacker knows the seed or can observe enough outputs
- Not handling SecureRandom initialization properly: The first call can be very slow as it gathers entropy
- Modulo bias with bounded ranges: Using
random.nextInt() % n
creates bias toward smaller numbers
Best Practices
Follow these guidelines for robust random number generation:
public class RandomBestPractices {
// Singleton pattern for application-wide random instances
private static final Random RANDOM = new Random();
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
// Pre-generate SecureRandom to avoid first-call delay
static {
SECURE_RANDOM.nextBytes(new byte[1]);
}
// Correct way to generate random in range (avoid modulo bias)
public static int randomInRange(int min, int max) {
return ThreadLocalRandom.current().nextInt(min, max);
}
// Secure token generation with proper length
public static String generateSecureToken(int byteLength) {
byte[] bytes = new byte[byteLength];
SECURE_RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
// Reservoir sampling for random selection from streams
public static <T> List<T> reservoirSample(Stream<T> stream, int sampleSize) {
List<T> reservoir = new ArrayList<>(sampleSize);
AtomicInteger count = new AtomicInteger(0);
stream.forEach(item -> {
int index = count.incrementAndGet();
if (index <= sampleSize) {
reservoir.add(item);
} else {
int randomIndex = ThreadLocalRandom.current().nextInt(index);
if (randomIndex < sampleSize) {
reservoir.set(randomIndex, item);
}
}
});
return reservoir;
}
}
Testing Random Code
Always use seeded generators for unit tests to ensure reproducible results:
public class RandomTestExample {
@Test
public void testRandomBehavior() {
// Use fixed seed for reproducible tests
Random testRandom = new Random(42L);
List<Integer> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(testRandom.nextInt(100));
}
// These results will always be the same
List<Integer> expected = Arrays.asList(33, 51, 13, 81, 28, 0, 36, 98, 16, 7);
assertEquals(expected, results);
}
@Test
public void testRandomDistribution() {
Random random = new Random(12345L);
Map<Integer, Integer> distribution = new HashMap<>();
// Generate many samples
for (int i = 0; i < 100000; i++) {
int value = random.nextInt(10);
distribution.merge(value, 1, Integer::sum);
}
// Check distribution is roughly uniform
for (int count : distribution.values()) {
assertTrue("Distribution should be roughly uniform",
Math.abs(count - 10000) < 1000);
}
}
}
Advanced Random Generation Techniques
Custom Probability Distributions
Sometimes you need non-uniform distributions. Here’s how to implement common patterns:
import java.util.concurrent.ThreadLocalRandom;
public class CustomDistributions {
// Generate normal distribution using Box-Muller transform
public static double nextGaussian(double mean, double stdDev) {
return ThreadLocalRandom.current().nextGaussian() * stdDev + mean;
}
// Generate exponential distribution
public static double nextExponential(double lambda) {
return -Math.log(1.0 - ThreadLocalRandom.current().nextDouble()) / lambda;
}
// Generate from custom discrete distribution
public static <T> T nextWeighted(Map<T, Double> weights) {
double totalWeight = weights.values().stream().mapToDouble(Double::doubleValue).sum();
double random = ThreadLocalRandom.current().nextDouble() * totalWeight;
double currentWeight = 0;
for (Map.Entry<T, Double> entry : weights.entrySet()) {
currentWeight += entry.getValue();
if (random <= currentWeight) {
return entry.getKey();
}
}
throw new IllegalStateException("Should not reach here");
}
public static void main(String[] args) {
// Test normal distribution
for (int i = 0; i < 10; i++) {
System.out.printf("Gaussian: %.2f%n", nextGaussian(100, 15));
}
// Test weighted selection
Map<String, Double> serverWeights = Map.of(
"server-1", 0.5,
"server-2", 0.3,
"server-3", 0.2
);
for (int i = 0; i < 5; i++) {
System.out.println("Selected: " + nextWeighted(serverWeights));
}
}
}
Random Data Generation for Testing
Generate realistic test data using random patterns:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ThreadLocalRandom;
public class TestDataGenerator {
private static final String[] FIRST_NAMES = {"John", "Jane", "Mike", "Sarah", "David", "Emma"};
private static final String[] LAST_NAMES = {"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia"};
private static final String[] DOMAINS = {"gmail.com", "yahoo.com", "outlook.com", "example.org"};
public static class User {
public final String firstName;
public final String lastName;
public final String email;
public final int age;
public final LocalDateTime createdAt;
public User(String firstName, String lastName, String email, int age, LocalDateTime createdAt) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.age = age;
this.createdAt = createdAt;
}
@Override
public String toString() {
return String.format("%s %s (%s), age %d, created %s",
firstName, lastName, email, age,
createdAt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
}
public static User generateRandomUser() {
ThreadLocalRandom random = ThreadLocalRandom.current();
String firstName = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)];
String lastName = LAST_NAMES[random.nextInt(LAST_NAMES.length)];
String email = firstName.toLowerCase() + "." + lastName.toLowerCase() +
random.nextInt(1000) + "@" +
DOMAINS[random.nextInt(DOMAINS.length)];
int age = random.nextInt(18, 80);
LocalDateTime createdAt = LocalDateTime.now().minusDays(random.nextInt(365));
return new User(firstName, lastName, email, age, createdAt);
}
public static void main(String[] args) {
System.out.println("Generated test users:");
for (int i = 0; i < 5; i++) {
System.out.println(generateRandomUser());
}
}
}
Understanding Java’s random number generation capabilities is crucial for building robust applications. Whether you’re implementing load balancing algorithms, generating test data, or securing user sessions, choosing the right random number generator and avoiding common pitfalls will make your applications more reliable and performant. The key is matching the generator to your use case: Random for simple needs, ThreadLocalRandom for concurrent applications, and SecureRandom when security matters.
For more detailed information about Java’s random number generators, check out the official Java Random documentation and the SecureRandom specification.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.