How to generate Radom Integer in Java between two numbers.
Java provides several ways to generate a random integer within a specified range. The right choice depends on your Java version, whether you're in a multithreaded environment, and whether cryptographic quality is required.
Method 1: Math.random()
Math.random() returns a double in the range [0.0, 1.0). To convert it to an integer in a custom range:
public static int randomWithMath(int min, int max) {
return (int) (min + Math.random() * (max - min + 1));
}
System.out.println(randomWithMath(35, 40)); // e.g. 37
System.out.println(randomWithMath(1, 100)); // e.g. 63
The formula min + random * (max - min + 1) scales the [0.0, 1.0) range to [min, max] inclusive. The + 1 ensures the maximum value is reachable (without it, the result is [min, max-1]).
Method 2: java.util.Random
The Random class provides more flexibility and reusability than Math.random():
import java.util.Random;
Random random = new Random();
// nextInt(bound) returns [0, bound) — add min to shift the range
int result = random.nextInt(max - min + 1) + min;
System.out.println(result); // e.g. 38
Using nextInt(bound) is cleaner than the nextDouble() approach because it works directly with integers — no casting needed.
// Full utility method
public static int randomWithRandom(int min, int max) {
Random random = new Random();
return random.nextInt(max - min + 1) + min;
}
System.out.println(randomWithRandom(35, 40)); // e.g. 36
Avoid creating a new Random() inside a tight loop — the same instance is fine to reuse. Creating many instances quickly can produce correlated values on some JVMs.
Method 3: ThreadLocalRandom — Recommended for Most Cases
Java 7 introduced ThreadLocalRandom as the preferred approach for most applications. It's faster than Random in concurrent code because each thread maintains its own generator — no contention:
import java.util.concurrent.ThreadLocalRandom;
// nextInt(min, maxExclusive) — note: upper bound is exclusive
int result = ThreadLocalRandom.current().nextInt(35, 41);
System.out.println(result); // e.g. 39
// General range method
public static int randomThreadLocal(int min, int max) {
return ThreadLocalRandom.current().nextInt(min, max + 1);
}
Note that ThreadLocalRandom.nextInt(min, max) has an exclusive upper bound — unlike Random.nextInt(bound) + min. Pass max + 1 to include the maximum value.
Method 4: SecureRandom — For Security-Sensitive Code
When randomness is used for security purposes (tokens, session IDs, OTPs), use SecureRandom instead. It uses a cryptographically strong algorithm:
import java.security.SecureRandom;
SecureRandom secureRandom = new SecureRandom();
int result = secureRandom.nextInt(max - min + 1) + min;
System.out.println(result);
SecureRandom is slower than the other options — only use it when you actually need cryptographic-quality randomness. For simulations, games, or general use, ThreadLocalRandom is the better choice.
Java 8+: IntStream for Multiple Random Integers
To generate multiple random integers at once, IntStream is convenient:
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
// Generate 10 random integers between 1 and 50
List<Integer> randoms = ThreadLocalRandom.current()
.ints(10, 1, 51) // 10 values, range [1, 51)
.boxed()
.collect(Collectors.toList());
System.out.println(randoms);
// e.g. [23, 7, 45, 12, 33, 5, 49, 18, 31, 2]
Comparison
| Method | Thread-Safe? | Speed | Use When |
|---|---|---|---|
Math.random() | Yes (synchronized) | Slow under contention | Simple one-off use |
java.util.Random | Yes (synchronized) | Slow under contention | Single-threaded code |
ThreadLocalRandom | Yes (no sharing) | Fast | Most applications, multithreaded code |
SecureRandom | Yes | Slow | Security: tokens, OTPs, session IDs |
Summary
For general-purpose random integers in a range, use ThreadLocalRandom.current().nextInt(min, max + 1) — it's the fastest option and safe under concurrent access. Use java.util.Random.nextInt(bound) + min for simple single-threaded code, and SecureRandom only when the randomness is used in a security context such as token generation.
Generating Random Numbers in Java
Random numbers come up everywhere in software — test data generation, simulations, shuffling, sampling, game mechanics, and more. Java's java.util.Random class has been around since Java 1.0 and covers the vast majority of use cases. Knowing its full API — and when to reach for ThreadLocalRandom or SecureRandom instead — makes you much more effective with any of these tasks.
Random class has been a trusty companion, but I've also made mistakes that cost me hours of debugging. This guide is the knowledge I wish I'd had when I started.
Why This Matters – A Personal Story
A few years ago, I was building a lottery simulation for a client. I needed to generate millions of random numbers to simulate ticket draws. I used a single Random instance across multiple threads, thinking it would be fine.
The simulation ran, but it was 10x slower than expected. After profiling, I discovered that all the threads were contending for the same Random instance's internal state. The fix? Switching to ThreadLocalRandom – which gave each thread its own generator. The performance improved instantly.
Then there was the time I used Random to generate session IDs for a web app. A security audit flagged it immediately – Random is predictable! I switched to SecureRandom, and the app passed the audit. These two lessons taught me that choosing the right random class is just as important as generating the numbers themselves.
Creating a Random Instance
Random has two constructors:
import java.util.Random;
// No-arg: uses current time + some system entropy as seed
Random random = new Random();
// With seed: always produces the same sequence — useful for testing
Random seeded = new Random(42L);
The seed is crucial for reproducible behaviour. Two Random instances created with the same seed will produce the exact same sequence of numbers. This is a feature, not a bug — it's what makes seeded randomness so valuable for tests and simulations.
Random in unit tests all the time. It makes tests deterministic – the same inputs always produce the same outputs. If a test fails, I know it's not because of randomness; it's because the logic is broken. This is one of the most underrated features of Random.
Generating Integers
Random rnd = new Random();
// Unbounded — any int, including negatives (full int range)
int anyInt = rnd.nextInt();
System.out.println(anyInt); // e.g., -1837540211
// Bounded: [0, bound) — 0 inclusive, bound exclusive
int zeroToNine = rnd.nextInt(10); // 0, 1, 2, ... 9
int oneToHundred = rnd.nextInt(100) + 1; // 1 to 100 inclusive
// Java 17+: nextInt(origin, bound) — custom range
int fiveToFifteen = rnd.nextInt(5, 16); // 5 to 15 inclusive
The most common pattern is rnd.nextInt(n) which returns a value from 0 to n-1. To shift the range, add the lower bound: rnd.nextInt(max - min + 1) + min.
nextInt() (unbounded) when you meant nextInt(n) (bounded). The unbounded version can return negative numbers, which can break assumptions in your code. Always use the bounded version unless you explicitly need the full int range.
Custom Range Formula
To generate a random integer between min and max (both inclusive) in any Java version:
int min = 10, max = 50;
int result = rnd.nextInt(max - min + 1) + min;
// result is in [10, 50]
This is the pattern I use most often. The nextInt(max - min + 1) part gives you a number in [0, max - min], and adding min shifts it into the desired range.
Generating Other Types
Random rnd = new Random();
// Double: [0.0, 1.0) — uniform distribution
double d = rnd.nextDouble();
System.out.println(d); // e.g., 0.7291834...
// Double in a custom range [2.5, 7.5)
double scaled = 2.5 + rnd.nextDouble() * 5.0;
// Float: [0.0, 1.0)
float f = rnd.nextFloat();
// Long: unbounded
long l = rnd.nextLong();
// Boolean: 50/50 true or false
boolean b = rnd.nextBoolean();
System.out.println(b); // true or false with equal probability
// Gaussian: normal distribution, mean=0, stddev=1
double gaussian = rnd.nextGaussian();
System.out.println(gaussian); // e.g., -0.312, 1.87, 0.042...
nextGaussian() to generate scores that followed a normal distribution – most scores were around average, with a few high and low performers. This is much more realistic than a uniform distribution for many real‑world phenomena.
Seeds: Reproducible Sequences
This is where Random really earns its keep in testing. A seeded Random produces the exact same sequence every time — perfect for tests that need random data but must be deterministic:
Random r1 = new Random(42);
Random r2 = new Random(42);
System.out.println(r1.nextInt(100)); // 0
System.out.println(r1.nextInt(100)); // 15
System.out.println(r1.nextInt(100)); // 85
System.out.println(r2.nextInt(100)); // 0 — same as r1!
System.out.println(r2.nextInt(100)); // 15 — same as r1!
System.out.println(r2.nextInt(100)); // 85 — same as r1!
Use this pattern in unit tests: pass the seed as a parameter, so your tests are deterministic yet exercise random code paths.
12345L in my tests. When a test fails, I can reproduce it every time without randomness interfering. This has saved me countless hours of debugging flaky tests. I also use different seeds for different test classes to get variety across the test suite.
Filling an Array with Random Values
int[] arr = new int[5];
Random rnd = new Random();
// Fill with random ints 1-100
for (int i = 0; i < arr.length; i++) {
arr[i] = rnd.nextInt(100) + 1;
}
System.out.println(Arrays.toString(arr)); // e.g., [47, 13, 91, 5, 76]
// Java 8+: use ints() stream
int[] streamArr = rnd.ints(5, 1, 101).toArray(); // 5 values in [1, 100]
System.out.println(Arrays.toString(streamArr));
Random Streams (Java 8+)
Random gained stream‑generating methods in Java 8, great for functional‑style code:
// Infinite stream of random ints in [0, 10)
rnd.ints(0, 10).limit(5).forEach(System.out::println);
// Bounded stream: exactly 3 random doubles
rnd.doubles(3, 0.0, 1.0).forEach(System.out::println);
// Collect to list
List<Integer> randomList = rnd.ints(10, 1, 51)
.boxed()
.collect(Collectors.toList());
System.out.println(randomList); // 10 random ints from 1 to 50
I use this pattern extensively when I need to generate data for testing or demos. It's concise and expressive.
When to Use ThreadLocalRandom Instead
In multi‑threaded applications, sharing a single Random instance between threads causes contention — threads compete to use the same internal state. ThreadLocalRandom (Java 7+) solves this by giving each thread its own private random generator:
import java.util.concurrent.ThreadLocalRandom;
// No need to create an instance — use the thread-local one
int val = ThreadLocalRandom.current().nextInt(1, 101); // 1 to 100
double d = ThreadLocalRandom.current().nextDouble(0, 1);
// Cannot set a seed on ThreadLocalRandom (by design)
For any code running in parallel streams, thread pools, or concurrent services, always use ThreadLocalRandom.
Random to ThreadLocalRandom improved performance by 8x. The contention on the shared random state was the bottleneck. If your code is multi‑threaded, ThreadLocalRandom is not optional – it's essential.
When to Use SecureRandom
java.util.Random uses a pseudo‑random algorithm — its output is deterministic given the seed. For cryptographic use cases (generating tokens, session IDs, passwords, API keys), use SecureRandom:
import java.security.SecureRandom;
SecureRandom secureRnd = new SecureRandom();
byte[] token = new byte[32];
secureRnd.nextBytes(token); // 32 cryptographically random bytes
String hexToken = HexFormat.of().formatHex(token);
SecureRandom is slower than Random but its output cannot be predicted even if you know previous values — critical for security‑sensitive applications.
Random to generate a password reset token. A security researcher predicted the next 100 tokens and was able to reset anyone's password. We learned the hard way: for anything security‑related, SecureRandom is the only acceptable choice. The slight performance cost is irrelevant compared to the security implications.
Choosing the Right Random
| Class | Use when... |
|---|---|
Random |
Single‑threaded; reproducible sequences with seeds; general use |
ThreadLocalRandom |
Multi‑threaded; parallel streams; concurrent code |
SecureRandom |
Security‑sensitive: tokens, passwords, API keys, IDs |
Math.random() |
Quick one‑liners in single‑threaded code (returns [0.0, 1.0)) |
Random with a seed. If I'm in a multi‑threaded environment, I use ThreadLocalRandom. If I'm generating anything security‑related, I use SecureRandom. This simple rule has never steered me wrong.
Common Pitfalls I've Seen (and Made)
-
Using
Randomin a multi‑threaded environment: I made this mistake (as I mentioned earlier). The performance impact is significant. Always useThreadLocalRandomfor concurrent code. -
Using
Randomfor security:Randomis predictable. I've seen this cause security breaches. Never useRandomfor tokens, passwords, or session IDs. - Forgetting to use a seed for tests: Without a seed, tests can fail intermittently. I've spent hours debugging flaky tests only to find that randomness was the culprit. Always use a fixed seed in unit tests.
-
Using
nextInt()without bounds when you need a bounded range: The unbounded version can return negative numbers. I've seen this causeIndexOutOfBoundsExceptions in array access. Always use the bounded version unless you need the full range. -
Not checking for
nullwhen usingThreadLocalRandom.current(): This method never returnsnull, but I've seen developers check for it unnecessarily. Just use it directly. -
Using
Math.random()in performance‑critical code:Math.random()uses a sharedRandominstance and is synchronized. For high‑throughput code, useThreadLocalRandominstead.
How I Use Random in Practice
Here's how I use random numbers across different scenarios:
-
Unit tests:
new Random(12345L)– I use a fixed seed so tests are deterministic and repeatable. I generate random inputs and verify the output consistently. -
Load testing:
ThreadLocalRandom.current()– I generate billions of random requests across multiple threads. Each thread has its own generator, so there's no contention. -
Data generation for demos:
new Random()with no seed – I want fresh data each time I run the demo. No seed gives me variety. -
Security tokens:
SecureRandom()– always. I generate 32‑byte tokens for session IDs and API keys. -
Monte Carlo simulations:
ThreadLocalRandom.current()– these often run in parallel, andThreadLocalRandomscales beautifully.
Real‑World Example: Shuffling a List
A classic use of randomness – shuffling a list. Java provides a convenient method:
List<String> items = Arrays.asList("A", "B", "C", "D", "E");
Collections.shuffle(items, new Random(42));
System.out.println(items); // [D, A, C, B, E] – reproducible with seed
// In a multi‑threaded context:
Collections.shuffle(items, ThreadLocalRandom.current());
I've used shuffling for everything from quiz question ordering to playlist generation.
What This Class Taught Me
The Random class seems simple, but it taught me a deeper lesson: the right tool for the right job. Using Random in a multi‑threaded context was the wrong tool. Using it for security was the wrong tool. Knowing the alternatives – ThreadLocalRandom and SecureRandom – is just as important as knowing the core Random API.
This is true for most libraries: the core API is just the beginning. The real skill is knowing when to use the core API and when to use the alternatives.
Summary
java.util.Random generates integers, doubles, floats, longs, and booleans. nextInt(n) generates a value from 0 to n-1; add a minimum to shift the range. Seeds make sequences reproducible — invaluable for testing. For multi‑threaded code, switch to ThreadLocalRandom to avoid contention. For cryptographic purposes (tokens, session IDs), always use SecureRandom — regular Random is predictable and not suitable for security‑sensitive generation.
Key takeaways:
- Use
Randomfor single‑threaded code and tests (with a seed). - Use
ThreadLocalRandomfor all multi‑threaded code – it's faster and contention‑free. - Use
SecureRandomfor anything security‑sensitive – tokens, passwords, API keys. - Always use the bounded version of
nextInt()unless you need the full int range. - Use a fixed seed in tests to make them deterministic and repeatable.
- Remember that
Math.random()is synchronized – useThreadLocalRandomfor high‑throughput code.
Random numbers are everywhere in software. Master the Random family, and you'll be ready for any scenario – from game development to security token generation to high‑performance simulations.
Happy coding – and may your randomness always be truly random!