Back to Blog
Java

Java Random nextInt: Usage, Bounds, and Thread Safety

java random nextint: Learn how to use Java's Random.nextInt() correctly, including range generation, bound handling, thread safety, and performance tradeoffs.

JavaRandomnextIntThreadLocalRandomSecureRandomRandom Number Generation
Illustration of Java Random nextInt generating a random integer within a specified range.

The java.util.Random class is the most direct way to generate random integers in Java, and its nextInt() method is the entry point for most applications. The method has two overloads: one that returns a random int across the full 32-bit range, and one that accepts a bound to restrict results to a non-negative range. Understanding how java random nextint behaves under different conditions is essential for writing correct and efficient code.

Using nextInt() Without a Bound

The no-argument overload, nextInt(), returns a uniformly distributed int value from -2147483648 to 2147483647. This is rarely what you need in practice because most applications require a positive number or a specific range. However, it is useful for generating a random bit pattern or for cases where the full integer space is acceptable.

import java.util.Random; Random random = new Random(); int value = random.nextInt(); // any int, including negative

The distribution is uniform across the entire 32-bit range, meaning each possible value has approximately the same probability. There is no bias in the algorithm for the unbounded version, so it is safe to use when you genuinely need any integer.

Generating Integers in a Range with nextInt(bound)

The more common overload, nextInt(int bound), returns a value from 0 (inclusive) to bound (exclusive). This is the standard way to generate a random integer in a zero-based range. To get a range like 1 through 10, you add an offset after the call.

Random random = new Random(); int zeroToNine = random.nextInt(10); // 0-9 int oneToTen = random.nextInt(10) + 1; // 1-10

The bound parameter must be positive. If you pass a value less than or equal to zero, the method throws IllegalArgumentException. This is a common source of bugs when the bound is computed dynamically and can become zero in edge cases.

Understanding the Bound Contract and Edge Cases

The bound is exclusive, so nextInt(bound) never returns bound itself. This is consistent with Java's convention for ranges in collections and streams, where the end index is exclusive. A common mistake is to assume the bound is inclusive, leading to off-by-one errors.

Another subtlety: if bound is 1, the method always returns 0. This is mathematically correct because the only valid value in the range [0, 1) is 0. In practice, this means you do not need a special case for a single-value range, but you should be aware that the call is still valid.

The implementation uses a rejection sampling algorithm to ensure uniformity. For powers of two, it can use a bitmask; for other bounds, it rejects values that would introduce bias. This means the method is efficient for typical bounds, but you should not assume a constant-time behavior for extremely large bounds—the rejection loop runs only when necessary, so the expected number of iterations is close to 1.

Thread Safety: Random vs ThreadLocalRandom

Instances of java.util.Random are not thread-safe. If multiple threads share a single Random instance and call nextInt() concurrently, the internal state may become corrupted, leading to incorrect results or even exceptions. The class uses a linear congruential generator with a 48-bit seed, and the update is not atomic.

For concurrent use, the recommended approach is ThreadLocalRandom, which provides a separate random generator for each thread. This eliminates contention and avoids the need for synchronization. The API is similar, but you obtain an instance via the static current() method.

import java.util.concurrent.ThreadLocalRandom; int randomNum = ThreadLocalRandom.current().nextInt(1, 11); // 1-10 inclusive

Notice that ThreadLocalRandom offers a convenience overload nextInt(origin, bound) that takes both a lower and upper bound. The lower bound is inclusive, and the upper bound is exclusive, so the example above generates values from 1 to 10. This overload is not available on Random, which only has the zero-based version.

Performance Considerations and Reusing Random Instances

Creating a new Random instance for every random number is wasteful because each constructor seeds the generator, often using the current time and a counter. The seeding process is relatively expensive compared to the actual generation. In performance-sensitive code, reuse a single Random instance across the application, but only if you are not sharing it across threads.

For single-threaded code, a single Random field is sufficient. For multi-threaded code, use ThreadLocalRandom to avoid both contention and the overhead of creating a new instance per call. The cost of nextInt(bound) itself is low—it involves a few arithmetic operations and an occasional rejection. The main performance concern is the creation and synchronization of the generator, not the method call itself.

If you need many random numbers in a tight loop, consider using ThreadLocalRandom even in a single-threaded context because it is specifically optimized for high throughput and avoids the overhead of a shared object. However, the difference is usually negligible unless you are generating millions of values.

When to Choose SecureRandom Instead

Random and ThreadLocalRandom are not cryptographically secure. Their algorithms are predictable if an attacker can observe enough output values. For security-sensitive applications—such as generating session IDs, tokens, or cryptographic keys—use java.security.SecureRandom, which provides a cryptographically strong random number generator.

SecureRandom also has a nextInt() method, but its implementation is different and typically slower because it gathers entropy from the operating system. You should not use it for non-security purposes where performance matters. The API is the same, so switching is straightforward.

import java.security.SecureRandom; SecureRandom secureRandom = new SecureRandom(); int secureValue = secureRandom.nextInt(100); // 0-99

A common mistake is to seed SecureRandom manually, which can weaken its security. The default constructor uses a reliable entropy source, so avoid overriding the seed unless you have a specific reason. In most cases, you should treat SecureRandom as the only acceptable choice when the generated numbers must be unpredictable to an adversary.

Choosing the right generator depends on your concurrency model and security requirements. For a simple single-threaded application, Random is sufficient. For concurrent code, ThreadLocalRandom is the standard choice. For security, SecureRandom is mandatory. Understanding these distinctions prevents subtle bugs and ensures your random number generation is both correct and efficient.

java random nextint: Practical Usage and Code Examples | RYUSLOG DEV