Back to Blog
Java

Java Math.random(): Generating Random Numbers in Java

Learn how to use Java's Math.random() to generate random doubles and integers, avoid off-by-one errors, and choose between Random, ThreadLocalRandom, and SecureRandom.

Math.randomRandom numbersJava Random classThreadLocalRandomSecureRandom
Java code snippet showing Math.random() usage with a dice roll example, illustrating random number generation.

Java's Math.random() is the simplest way to generate a random double in the range [0.0, 1.0). It requires no setup and works in any Java program. Its limitations become apparent when you need integers, specific ranges, or better concurrency behavior.

What Math.random() Actually Returns

Math.random() returns a double value greater than or equal to 0.0 and less than 1.0. The upper bound is exclusive, so 1.0 is never returned. This is the same contract used by most random number generators in Java. The method internally delegates to a single static java.util.Random instance.

Because the return type is double, you cannot directly use it as an integer index or a count. You must scale and cast the result. The typical pattern for an integer in the range [0, n) is:

int randomInt = (int) (Math.random() * n);

This works because the product is in [0, n) and the cast truncates the fractional part. For example, n = 6 gives values 0 through 5, which is useful for selecting an element from a zero-based array.

Generating Integers in a Range

To get an integer between min and max inclusive, you need to adjust the formula. The range size is max - min + 1. Multiply Math.random() by that size, add min, and cast:

int randomInRange = min + (int) (Math.random() * (max - min + 1));

For a dice roll where min = 1 and max = 6, this produces 1 through 6. The addition of 1 in the size is the most common source of off-by-one errors. If you forget it, you get values from min to max - 1.

Avoiding Common Off-by-One Errors

The exclusive upper bound of Math.random() is easy to forget. When you write (int) (Math.random() * 10), you get 0 through 9, not 1 through 10. If you need 1 through 10, add 1 after the cast.

Another mistake is using Math.round() instead of casting. Math.round() returns a long and rounds to the nearest integer, which changes the distribution. The cast to int truncates, which is what you want for a uniform integer distribution. For example, (int) (Math.random() * 6) gives each value 0 to 5 with equal probability, while Math.round(Math.random() * 5) would give 0 and 5 half as often as the middle values.

When to Use java.util.Random Instead

Math.random() is convenient, but it has limitations. You cannot control the seed, and the shared generator can become a contention point under heavy multithreaded use. For most applications, java.util.Random offers the same API with more control.

import java.util.Random; Random random = new Random(); int randomInt = random.nextInt(10); // 0 to 9 double randomDouble = random.nextDouble(); // 0.0 to 1.0

Random provides nextInt(int bound), nextLong(), nextBoolean(), and nextFloat(). The nextInt(bound) method is more efficient than the multiply-and-cast pattern because it avoids floating-point arithmetic and handles the bound internally.

You can also provide a seed for reproducible sequences:

Random seeded = new Random(42L);

This is useful for tests or simulations where you need the same sequence on every run.

Thread Safety and Concurrent Use

Math.random() uses a single static java.util.Random instance. That instance is thread-safe, so concurrent calls will not produce incorrect values. However, because every thread shares the same generator, heavy concurrent use causes contention and can slow down random-number generation. If many threads need random numbers at a high rate, prefer ThreadLocalRandom:

import java.util.concurrent.ThreadLocalRandom; int randomInt = ThreadLocalRandom.current().nextInt(1, 7);

ThreadLocalRandom gives each thread its own generator, eliminating contention. It also supports inclusive lower and exclusive upper bounds directly. The nextInt(origin, bound) method takes the lower bound inclusive and the upper bound exclusive, so nextInt(1, 7) returns 1 through 6.

Security Considerations for Random Numbers

Neither Math.random() nor java.util.Random is cryptographically secure. Both use a predictable linear congruential generator, so an attacker who observes enough output can predict future values. For security-sensitive tasks such as generating tokens, passwords, or session IDs, use SecureRandom.

import java.security.SecureRandom; SecureRandom secureRandom = new SecureRandom(); byte[] bytes = new byte[16]; secureRandom.nextBytes(bytes);

SecureRandom uses a cryptographically strong algorithm and can be slower, but that is the correct trade-off when security matters. Do not use Math.random() for anything that requires unpredictability.

Performance and Allocation Costs

Math.random() avoids allocating a new generator each call, but it still involves a method call and floating-point multiplication. For most applications this overhead is negligible. However, if you are generating millions of random numbers in a tight loop, ThreadLocalRandom can be faster because it avoids the shared static state and resulting contention.

Random instances are cheap to create but not free. If you create a new Random per call, you pay allocation and initialization costs. Reuse a single instance when you need a sequence of random numbers, but remember that shared generator state can still cause contention under concurrency. ThreadLocalRandom is the best choice for high-volume concurrent generation.

Choosing the Right Random API

The choice depends on your requirements:

  • Use Math.random() for quick scripts or when you only need a few random doubles and do not need a seed or heavy concurrency.
  • Use java.util.Random when you need a seed or a simple API for integers and other types.
  • Use ThreadLocalRandom in multi-threaded code or when performance is critical.
  • Use SecureRandom for any security-related randomness.

Understanding the differences prevents subtle bugs and ensures your random numbers behave as expected in production.

Java Math.random(): Practical Usage and Code Examples | RYUSLOG DEV