• Home
  • About
    • Thoughts To Pen photo

      Thoughts To Pen

      My thoughts on Computer Programming || Psychology || Personal Finances || & much more...

    • Learn More
    • Twitter
    • Instagram
    • Github
    • StackOverflow
  • Posts
    • All Posts
    • All Tags
  • Projects
  • Portfolio
  • Resources
  • About

Enhanced Pseudo-Random Number Generators (Java 17)

25 Nov 2025

Java 17 introduces Enhanced Pseudo-Random Number Generators through JEP 356, providing a modern, flexible framework for generating random numbers. This update brings new interfaces, multiple algorithm implementations, and better support for stream-based operations, addressing limitations of the legacy java.util.Random class.

The Problem with Legacy Random

The traditional java.util.Random class has several limitations:

Random random = new Random();
int value = random.nextInt(100);  // Limited to one algorithm

Issues:

  • Only one algorithm (Linear Congruential Generator)
  • No easy way to switch algorithms
  • Limited support for modern use cases
  • Not designed for streams and parallel processing

The New Framework

Java 17 introduces a hierarchy of interfaces and implementations:

Core Interfaces

  1. RandomGenerator - Base interface for all generators
  2. RandomGenerator.SplittableGenerator - For parallel streams
  3. RandomGenerator.JumpableGenerator - For jumping ahead in sequence
  4. RandomGenerator.LeapableGenerator - For large jumps
  5. RandomGenerator.ArbitrarilyJumpableGenerator - For arbitrary jumps

New Factory Method

RandomGenerator generator = RandomGenerator.of("L64X128MixRandom");
int value = generator.nextInt(100);

Available Algorithms

Java 17 provides multiple high-quality PRNG algorithms:

AlgorithmBest ForThread-Safe
L32X64MixRandomGeneral purpose, small stateNo
L64X128MixRandomGeneral purpose, medium stateNo
L64X256MixRandomHigh-quality randomnessNo
L64X1024MixRandomHighest quality, large stateNo
L128X128MixRandomVery high qualityNo
L128X256MixRandomExcellent qualityNo
L128X1024MixRandomBest quality, largest stateNo
Xoroshiro128PlusPlusFast, good qualityNo
Xoshiro256PlusPlusFast, better qualityNo
RandomLegacy compatibilityYes
SecureRandomCryptographic securityYes

Basic Usage

Creating Generators

// Using factory method with algorithm name
RandomGenerator rng1 = RandomGenerator.of("L64X128MixRandom");

// Using default algorithm
RandomGenerator rng2 = RandomGenerator.getDefault();

// Using specific implementation class
RandomGenerator rng3 = new Xoshiro256PlusPlus();

// Legacy Random still works
Random legacyRandom = new Random();

Generating Random Values

RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");

// Integers
int randomInt = rng.nextInt();
int boundedInt = rng.nextInt(100);  // 0 to 99
int rangedInt = rng.nextInt(10, 20); // 10 to 19

// Longs
long randomLong = rng.nextLong();
long boundedLong = rng.nextLong(1000L);

// Doubles
double randomDouble = rng.nextDouble();  // 0.0 to 1.0
double rangedDouble = rng.nextDouble(10.0, 20.0);

// Booleans
boolean randomBoolean = rng.nextBoolean();

// Bytes
byte[] bytes = new byte[16];
rng.nextBytes(bytes);

Stream-Based Operations

One of the most powerful features is stream support:

Infinite Streams

RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");

// Stream of random integers
rng.ints()
   .limit(10)
   .forEach(System.out::println);

// Stream of bounded integers
rng.ints(100)  // limit to 100 values
   .limit(10)
   .forEach(System.out::println);

// Stream with range
rng.ints(10, 1, 100)  // 10 values between 1 and 99
   .forEach(System.out::println);

Practical Stream Examples

// Generate random passwords
String password = RandomGenerator.of("L64X128MixRandom")
    .ints(12, 33, 127)  // 12 printable ASCII characters
    .collect(StringBuilder::new,
             StringBuilder::appendCodePoint,
             StringBuilder::append)
    .toString();

// Generate test data
List<Integer> testScores = RandomGenerator.of("L64X128MixRandom")
    .ints(100, 0, 101)  // 100 scores from 0 to 100
    .boxed()
    .collect(Collectors.toList());

// Simulate dice rolls
Map<Integer, Long> diceRolls = RandomGenerator.of("L64X128MixRandom")
    .ints(10000, 1, 7)  // 10,000 rolls of a 6-sided die
    .boxed()
    .collect(Collectors.groupingBy(
        Function.identity(),
        Collectors.counting()
    ));

Splittable Generators for Parallel Processing

For parallel streams, use splittable generators:

RandomGenerator.SplittableGenerator splitRng = 
    RandomGeneratorFactory.of("L64X128MixRandom")
        .create(System.currentTimeMillis());

// Parallel stream with independent generators
List<Double> parallelRandoms = IntStream.range(0, 1000000)
    .parallel()
    .mapToObj(i -> splitRng.split().nextDouble())
    .collect(Collectors.toList());

Real-World Examples

1. Monte Carlo Simulation

public class MonteCarloSimulation {
    
    public static double estimatePi(int iterations) {
        RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");
        
        long insideCircle = rng.doubles(iterations)
            .map(x -> new double[]{x, rng.nextDouble()})
            .filter(point -> {
                double x = point[0];
                double y = point[1];
                return (x * x + y * y) <= 1.0;
            })
            .count();
        
        return 4.0 * insideCircle / iterations;
    }
    
    public static void main(String[] args) {
        double pi = estimatePi(1_000_000);
        System.out.println("Estimated π: " + pi);
        System.out.println("Actual π: " + Math.PI);
        System.out.println("Error: " + Math.abs(pi - Math.PI));
    }
}

2. Random Data Generator

public class TestDataGenerator {
    private final RandomGenerator rng;
    
    public TestDataGenerator() {
        this.rng = RandomGenerator.of("L64X128MixRandom");
    }
    
    public String randomEmail() {
        String username = rng.ints(8, 'a', 'z' + 1)
            .collect(StringBuilder::new,
                     StringBuilder::appendCodePoint,
                     StringBuilder::append)
            .toString();
        return username + "@example.com";
    }
    
    public String randomPhoneNumber() {
        return String.format("(%03d) %03d-%04d",
            rng.nextInt(200, 1000),
            rng.nextInt(200, 1000),
            rng.nextInt(10000)
        );
    }
    
    public LocalDate randomBirthdate() {
        long minDay = LocalDate.of(1950, 1, 1).toEpochDay();
        long maxDay = LocalDate.of(2005, 12, 31).toEpochDay();
        long randomDay = rng.nextLong(minDay, maxDay + 1);
        return LocalDate.ofEpochDay(randomDay);
    }
    
    public List<String> randomNames(int count) {
        String[] firstNames = {"Alice", "Bob", "Charlie", "Diana", "Eve"};
        String[] lastNames = {"Smith", "Johnson", "Williams", "Brown", "Jones"};
        
        return rng.ints(count, 0, firstNames.length)
            .mapToObj(i -> firstNames[i] + " " + 
                          lastNames[rng.nextInt(lastNames.length)])
            .collect(Collectors.toList());
    }
}

3. Weighted Random Selection

public class WeightedRandomSelector<T> {
    private final List<T> items;
    private final List<Double> cumulativeWeights;
    private final RandomGenerator rng;
    
    public WeightedRandomSelector(Map<T, Double> weightedItems) {
        this.rng = RandomGenerator.of("L64X128MixRandom");
        this.items = new ArrayList<>(weightedItems.keySet());
        this.cumulativeWeights = new ArrayList<>();
        
        double sum = 0;
        for (T item : items) {
            sum += weightedItems.get(item);
            cumulativeWeights.add(sum);
        }
    }
    
    public T select() {
        double random = rng.nextDouble() * cumulativeWeights.get(cumulativeWeights.size() - 1);
        
        for (int i = 0; i < cumulativeWeights.size(); i++) {
            if (random <= cumulativeWeights.get(i)) {
                return items.get(i);
            }
        }
        
        return items.get(items.size() - 1);
    }
    
    public List<T> selectMultiple(int count) {
        return IntStream.range(0, count)
            .mapToObj(i -> select())
            .collect(Collectors.toList());
    }
}

// Usage
Map<String, Double> items = Map.of(
    "Common", 50.0,
    "Uncommon", 30.0,
    "Rare", 15.0,
    "Epic", 4.0,
    "Legendary", 1.0
);

WeightedRandomSelector<String> selector = new WeightedRandomSelector<>(items);
List<String> drops = selector.selectMultiple(100);

4. Shuffling Collections

public class CollectionShuffler {
    
    public static <T> void shuffle(List<T> list, RandomGenerator rng) {
        for (int i = list.size() - 1; i > 0; i--) {
            int j = rng.nextInt(i + 1);
            Collections.swap(list, i, j);
        }
    }
    
    public static void main(String[] args) {
        List<String> deck = new ArrayList<>();
        for (String suit : List.of("♠", "♥", "♦", "♣")) {
            for (String rank : List.of("A", "2", "3", "4", "5", "6", 
                                      "7", "8", "9", "10", "J", "Q", "K")) {
                deck.add(rank + suit);
            }
        }
        
        RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");
        shuffle(deck, rng);
        
        System.out.println("Shuffled deck: " + deck.subList(0, 5));
    }
}

Choosing the Right Algorithm

For General Use

RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");

For High-Quality Randomness

RandomGenerator rng = RandomGenerator.of("L128X256MixRandom");

For Speed

RandomGenerator rng = RandomGenerator.of("Xoshiro256PlusPlus");

For Cryptographic Security

RandomGenerator rng = new SecureRandom();

Best Practices

1. Reuse Generator Instances

// Good - reuse the generator
RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");
for (int i = 0; i < 1000; i++) {
    int value = rng.nextInt(100);
}

// Bad - creating new generators is expensive
for (int i = 0; i < 1000; i++) {
    RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");
    int value = rng.nextInt(100);
}

2. Use Streams for Bulk Generation

// Good - efficient stream-based generation
List<Integer> numbers = RandomGenerator.of("L64X128MixRandom")
    .ints(1000, 0, 100)
    .boxed()
    .collect(Collectors.toList());

// Less efficient - loop-based generation
List<Integer> numbers2 = new ArrayList<>();
RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");
for (int i = 0; i < 1000; i++) {
    numbers2.add(rng.nextInt(100));
}

3. Choose Appropriate Algorithm

Don’t use SecureRandom for non-security purposes - it’s much slower.

Conclusion

Java 17’s Enhanced Pseudo-Random Number Generators provide a modern, flexible framework for generating random numbers. With multiple high-quality algorithms, excellent stream support, and better parallel processing capabilities, this feature addresses the limitations of legacy Random while maintaining backward compatibility. Whether you’re building simulations, generating test data, or implementing game mechanics, the new PRNG framework offers the tools you need for high-quality randomness.



programmingjavajava17 Share Tweet Msg