Use ThreadLocalRandom.current().nextInt(min, max + 1). The second argument is exclusive, so the + 1 is what lets max itself come up: nextInt(1, 7) rolls a die from 1 to 6. For numbers that repeat on every run, use a seeded new Random(42), and for passwords or tokens use SecureRandom.
Every random API in Java draws from a half-open range: the lower end is included and the upper end is not. That one rule is behind most of the bugs in this area, from dice that never roll a 6 to the classic min + rand.nextInt(max - min + 1) formula that every older answer quotes. Since Java 17 the generators share the RandomGenerator interface, so Random, ThreadLocalRandom and SecureRandom all accept the same nextInt(origin, bound) call. Random output changes on every run, so the examples below either use a fixed seed or print checks such as allInRange: true instead of the numbers. Each one runs on this page: hit Run, then edit the code and run it again.
1ThreadLocalRandom.current().nextInt(min, max + 1)Recommended
ThreadLocalRandom (Java 7+) is the generator to reach for in ordinary code. There is nothing to create or store: current() hands back the calling thread's own instance, so it is fast and never contended between threads. Its nextInt(origin, bound) returns a number from origin up to, but not including, bound. For an inclusive range, pass max + 1.
Output
Prints roll is between 1 and 6: true, allInRange: true and min seen: 1, max seen: 6: over 10,000 draws every face shows up. Drop the + 1 and the result is without + 1, max seen: 5. That is the off-by-one behind the Stack Overflow question, and it fails silently. Ranges that cross zero work the same way (-10..10: true), and an empty range throws IllegalArgumentException: bound must be greater than origin instead of returning something. Call ThreadLocalRandom.current() each time rather than keeping the result in a field that other threads might use.
2Random, seeds and min + nextInt(max - min + 1)
java.util.Random is the original generator, and the one to use when you need the same numbers every run: give it a seed and the sequence is fixed, which is what tests, simulations and procedurally generated levels want. Since Java 17 it has nextInt(origin, bound) too. On Java 8 to 16 you only have nextInt(n), which returns 0 to n - 1, so you shift it by min. That is the classic min + rand.nextInt(max - min + 1).
Output
Both loops print 17 14 18 15 13 18 11 12, on this run and every other, because the seed is fixed. The shifted version without the + 1 tops out at nextInt(max - min), max seen: 19, one short of 20. The last line shows the Java 17+ RandomGenerator interface picking a newer LXM algorithm by name (L64X128MixRandom in range: true); code that takes a RandomGenerator parameter works with any of them. Create one Random and reuse it rather than calling new Random() for every number.
3Math.random() and doubles in a range
Math.random() returns a double from 0.0 (inclusive) to 1.0 (exclusive). To turn it into an int from min to max, multiply by the number of values, cast, then add min: (int) (Math.random() * (max - min + 1)) + min. It works, but the parentheses are easy to get wrong. For doubles in a range, Java 17+ has nextDouble(origin, bound); before that, scale nextDouble() yourself.
Output
The scaled version prints Math.random() scaled, min seen: 1, max seen: 6. Move the parentheses and you get (int) Math.random() * 6 + 1 = 1 on every run: the cast applies to Math.random() alone, truncating it to 0 before the multiply. The seeded doubles are 8.63781840016434 -> 8.64, 8.416117358799227 -> 8.42 and 6.543597276663299 -> 6.54, and the hand-scaled version gives the same first value, scaled by hand: 8.63781840016434. String.format only rounds for display; see round to 2 decimal places for rounding the value itself.
4Streams of random numbers: ints(count, min, max)
When you need many numbers at once, every generator has ints(count, origin, bound), which returns an IntStream with the same exclusive bound. There are longs and doubles versions too. Leave out the count and the stream is endless, which is handy with distinct() and limit() for picking numbers without repeats.
Output
Ten seeded rolls print [3, 4, 1, 3, 1, 2, 6, 3, 2, 6] (print an array covers Arrays.toString), five scores print [15, 59, 4, 89, 11], and the six lottery numbers are [20, 26, 30, 32, 34, 35]. The dice counts, {1=9932, 2=9943, 3=10147, 4=9907, 5=9970, 6=10101}, are all close to 10,000, so the range is uniform. distinct() on an endless stream keeps drawing until it has six different values; if you want most of a small range, shuffling a list is more direct (see the FAQ).
5SecureRandom: tokens and a random alphanumeric string
Random and ThreadLocalRandom are predictable: someone who sees a few outputs can work out the rest. For passwords, reset links, session IDs and one-time codes use java.security.SecureRandom. It extends Random, so it has the same nextInt(origin, bound). A random alphanumeric string is just a loop that picks random characters from an alphabet. Writing it against RandomGenerator lets the same method take a seeded Random in tests and a SecureRandom in production. Apache Commons Lang has RandomStringUtils for this, but you don't need a library.
Output
The seeded string is Gpi2C7DgXDiA both times, which is exactly why a seeded Random must never make secrets. The SecureRandom lines change every run, so they print checks: code: 12 chars, alphanumeric: true, base64url token: 43 chars, url-safe: true, hex token: 64 chars and otp: 6 digits, numeric: true. For tokens, encoding random bytes is simpler than picking characters: 32 bytes is 256 bits of randomness, 43 characters in URL-safe Base64 or 64 in hex (HexFormat is Java 17+). Create one SecureRandom and reuse it.
6Which should you use?
| Method | Range | Repeatable | Best for |
|---|---|---|---|
| ThreadLocalRandom.current().nextInt(min, max + 1) | min to max | No (cannot be seeded) | Everyday random numbers, any thread |
| new Random(seed).nextInt(min, max + 1) | min to max (Java 17+) | Yes, with a seed | Tests, simulations, games |
| min + rand.nextInt(max - min + 1) | min to max | Yes, with a seed | Java 8 to 16 |
| (int) (Math.random() * (max - min + 1)) + min | min to max | No | Old code; easy to get the parentheses wrong |
| rng.ints(count, min, max + 1) | min to max | With a seeded Random | Many numbers at once, stream pipelines |
| new SecureRandom().nextInt(min, max + 1) | min to max | No (by design) | Passwords, tokens, one-time codes |
Frequently asked questions
Is the upper bound of nextInt inclusive or exclusive in Java?
Exclusive. nextInt(origin, bound) returns a value from origin up to bound - 1, and nextInt(n) returns 0 to n - 1. So nextInt(1, 6) never returns 6; for an inclusive range from min to max, call nextInt(min, max + 1) or min + rand.nextInt(max - min + 1). The same rule applies to nextDouble, ints, longs and doubles.
How do I get the same random numbers every time?
Use a seeded Random: new Random(42) produces the same sequence on every run, because the algorithm of java.util.Random is part of its specification. ThreadLocalRandom cannot be seeded, and calling ThreadLocalRandom.current().setSeed(42) throws java.lang.UnsupportedOperationException. Keep the same method calls when you rely on a seeded sequence: with new Random(42), eight calls to nextInt(0, 8) give 5 7 1 0 4 3 3 5 but eight calls to nextInt(8) give 5 0 5 0 2 7 2 5.
Why not use Math.abs(rand.nextInt()) % n?
Because it can return a negative number. Math.abs(Integer.MIN_VALUE) is -2147483648, since the positive value does not fit in an int, so one input in about four billion slips through. The modulo also favours small results slightly whenever n does not divide 2^32 evenly. rand.nextInt(n) and nextInt(origin, bound) avoid both problems.
How do I generate random numbers without duplicates?
To pick a few values from a large range, take a stream and drop repeats: rng.ints(1, 50).distinct().limit(6). To use most or all of a small range, shuffle a list instead: shuffling the numbers 1 to 10 with Collections.shuffle(list, new Random(42)) gives [5, 7, 3, 2, 8, 10, 9, 6, 4, 1]. Leave out the Random argument for a different order each run.
Can I use UUID.randomUUID() as a random string?
Yes, when the format does not matter. UUID.randomUUID().toString() is 36 characters, 32 hex digits plus four hyphens, and it is generated with a cryptographically strong generator. It carries 122 random bits, because six bits are fixed version and variant markers. For a specific length or alphabet, pick characters with SecureRandom, or Base64-encode random bytes.
What is the difference between Random, ThreadLocalRandom and SecureRandom?
Random is seedable and thread-safe, but threads sharing one instance compete for it. ThreadLocalRandom gives each thread its own generator, so it is the fastest choice for general use, but it cannot be seeded. SecureRandom is slower and unpredictable, which is what you want for passwords, tokens and keys. All three implement the Java 17+ RandomGenerator interface, so they share methods such as nextInt(origin, bound) and ints(count, origin, bound).