Java

Utility Classes: Objects, Math, Random, and UUID

Executive Summary

The JDK’s utility classes start with Objects, which supplies the null-aware operations the language forgot. These include equals for safe comparison (true when both null, safe when one is), deepEquals for arrays, requireNonNull for fail-fast argument validation, and toString with defaults. Similarly, Math supplies exact arithmetic (addExact and friends throw ArithmeticException instead of silently wrapping, the Part 1 overflow lesson mechanized), floorDiv and floorMod (the negative-number parity fix), min, max, abs, and, final in Java 21, clamp. Random generates for games and tests, with seeds for reproducibility. Meanwhile, ThreadLocalRandom.current() avoids contention under threads, and SecureRandom generates everything an attacker might predict, tokens, session IDs, one-time codes.

Finally, UUID.randomUUID gives version-4 identifiers, 122 random bits in a canonical text form. They are right for correlation IDs and wrong for most database primary keys. Your own utility classes follow one pattern: final class, private constructor, static methods, and no state of any kind.

Objects: The Null-Aware Helpers

Objects is what java.lang.Object would contain if null had been designed in from the start, per the Objects documentation. In fact, you already met its two stars: Objects.equals carried the Object article’s equals recipe, and Objects.requireNonNull guarded the Optional article’s argument boundaries. For example, here is the wrong code first, the hand-rolled version that appears in every legacy codebase:

// WRONG: hand-rolled null-safe equality, reinvented forty different ways
boolean same = (a == b) || (a != null && a.equals(b));   // reversed args, missed
                                                        // null cases, subtle bugs

// RIGHT: one library call with the same semantics everywhere
boolean same2 = Objects.equals(a, b);   // true when both null, false when one is

The working set is small:

Objects.equals(a, b);              // null-safe equals
Objects.deepEquals(arr1, arr2);    // content equality for nested arrays
Objects.requireNonNull(title, "title is required");   // fail fast, named value
Objects.toString(ref, "none");      // null-safe toString with a default
Objects.isNull(ref);                // exists; == null is usually clearer
Objects.hash(a, b, c);              // hash from several fields

One subtlety earns a flag because it connects to the Object article’s pair rule. Objects.hash(a, b, c) hashes a small array of the arguments, and its results are consistent with a multi-field equals, the way records and IDE-generated code use it. However, do not mix it with hashCode on a single object in the same equals and hashCode pair. Otherwise, equal objects hash differently, and hash collections quietly misbehave.

Meanwhile, requireNonNull deserves its production billing. It is the argument-check tool that returns its own argument, so validation composes with assignment in one line:

public Book(Isbn isbn, String title) {
    this.isbn = Objects.requireNonNull(isbn, "isbn is required");
    this.title = Objects.requireNonNull(title, "title is required");
}

That is the constructor-validation discipline from Part 2 with the ceremony removed. The check, the message, and the assignment fit in one line, and the NullPointerException it throws names the exact argument.

Math: Arithmetic That Behaves

Math is the static collection of numeric functions. In practice, its most valuable members are the ones that fix the silent traps from the Part 1 types article. For example, here is the wrong code first:

// WRONG: silent overflow, the Part 1 lesson, still shipping today
int total = Integer.MAX_VALUE + 1;   // wraps to -2147483648: no exception, no warning

// RIGHT: exact arithmetic that refuses to lie
int total2 = Math.addExact(Integer.MAX_VALUE, 1);   // ArithmeticException:
                                                    // "integer overflow"

And the parity trap, fixed the same way:

-7 % 2;                  // -1: remainder keeps the dividend's sign (Part 1 lesson)
Math.floorMod(-7, 2);    // 1: always non-negative for positive divisor: the parity fix
Math.floorDiv(-7, 2);    // -4: division toward negative infinity, consistent with floorMod
Method family Job Why it exists
addExact, subtractExact, multiplyExact Arithmetic that throws on overflow Mechanizes the Part 1 silent-wrap lesson
floorDiv, floorMod Division and remainder toward negative infinity -7 % 2 is -1; parity checks need floorMod
min, max, abs Obvious, overloaded for all primitives Replaces hand-rolled ternaries
clamp(value, min, max) Bound a value to a range Final in Java 21
pow, sqrt, cbrt, hypot Powers and roots, double domain pow(x, 0.5) is not a substitute for sqrt
floor, ceil, round Boundary rounding round returns long for double: a classic surprise
random A double in [0, 1) Discouraged: use Random or ThreadLocalRandom

That last row is worth saying twice. Math.random exists, but the guessing game article’s guidance stands. Integer ranges belong to Random.nextInt(origin, bound), which states the range exactly instead of casting a scaled double. Math keeps it only because 1995 code still compiles.

Random, ThreadLocalRandom, SecureRandom: Three Contracts

The guessing game article introduced Random and its bound rules. However, the production half is knowing that the JDK ships three generators with three different promises:

// Random: games, sampling, test data; seedable for reproducibility
var rng = new Random(42);              // seed 42: same sequence on every run
int die = rng.nextInt(1, 7);            // 1 to 6 inclusive

// ThreadLocalRandom: concurrent hot paths, one generator per thread
int dice = java.util.concurrent.ThreadLocalRandom.current().nextInt(1, 7);

// SecureRandom: anything an attacker might predict
var secure = new java.security.SecureRandom();
byte[] token = new byte[16];
secure.nextBytes(token);               // cryptographic strength, no seed control
Generator Right for Wrong for Notes
Random Games, simulations, seeded tests Tokens, secrets, anything predictable Seeds make failures reproducible
ThreadLocalRandom Concurrent generation under load Seeded tests, security Part 5’s pools use it internally
SecureRandom Tokens, session IDs, one-time codes Nothing; it is slower by design Part 9’s security article relies on it

The boundary between the first and third rows is security, not performance, exactly as the guessing game argued. After all, a Random sequence is predictable from its output, which is a feature for tests and a vulnerability for tokens. As a result, choosing SecureRandom is a design decision the security article revisits in Part 9.

UUID: Identity Without a Counter

UUID.randomUUID() generates a version-4 identifier: 122 random bits in a canonical form of 36 characters with hyphens. It needs no coordination with any other generator, per the UUID documentation:

UUID id = UUID.randomUUID();          // e.g. 7f2c9b3e-4a1d-4f6b-8c2e-9d3a1f5b7e21
String asText = id.toString();        // canonical lowercase form
UUID parsed = UUID.fromString(asText); // round-trips losslessly

boolean same = id.equals(parsed);     // true: content equality, like the records rule

record Correlation(UUID traceId, Instant at) { }   // a value carrying a value

The production uses are specific. For example, correlation IDs tie a request across services and log lines. Other uses are external identifiers that must not leak counts, and distributed data that cannot ask a central counter for the next number. The wrong use is equally specific and comes from the Part 1 types article’s lesson about growth. A UUID primary key on a large table carries no ordering, so the database index fragments instead of appending. Meanwhile, UUID.randomUUID().toString() in the log is canonical while UUID fields keep type safety in the code. In short, choose UUID for coordination and longs for sequence, and never confuse the two jobs.

Designing Your Own Utility Class

The JDK’s own utility classes model the pattern, and every codebase eventually writes one. For example, here is the wrong code first, the instantiable fake:

// WRONG: instantiable "utility": a class with no reason to exist
public class TextUtils {
    public String slug(String input) {   // why is this an instance method?
        return input.strip().toLowerCase().replace(' ', '-');
    }
}
new TextUtils().slug("Effective Java");   // allocation serving no purpose

// RIGHT: final class, private constructor, static methods, no state
public final class TextUtils {
    private TextUtils() { }              // no instance can ever exist

    public static String slug(String input) {
        return input.strip().toLowerCase().replace(' ', '-');
    }
}
TextUtils.slug("Effective Java");         // effective-java

In practice, three rules carry all the weight. Final, because a utility class has nothing to override and nothing to extend. Private constructor, because the class exists to group static functions, not to be instantiated, and the private constructor makes that a compile-time fact. No fields, especially no mutable static fields. After all, state is where the encapsulation article’s Part 5 warning lives, and a utility class with a mutable static field is a global variable with a class name.

How Real Systems Do This

These four utility classes are quiet infrastructure in every production service. For example, Objects.equals backs the equals implementations of half the codebase, and Objects.requireNonNull guards constructors and setters. Similarly, Math.floorMod powers scheduling arithmetic. UUIDs also ride along every request as correlation IDs that let one incident be followed across services, logs, and dashboards, the practice the logging article builds on in Part 7.

The audit that sold me on this shelf was a codebase with about forty hand-rolled null-safe equals implementations. Reading them as a set found five subtle bugs. For example, one had the arguments reversed, so it returned true for every non-null pair and quietly broke a deduplication job. As a result, replacing all forty with Objects.equals took an hour, deleted two hundred lines, and made the semantics uniform everywhere. The library version had been one import away the entire time. That is the general lesson of this article. Before writing a helper, spend one minute checking whether the JDK already shipped it, because its version has been reviewed by more people than will ever read your team’s version.

Moreover, the same lesson scales up. Random with a fixed seed is how the Part 7 test articles will make flaky-looking probabilistic code deterministic. Similarly, ThreadLocalRandom.current() is what Part 5’s thread pools call when they need a number without contention. Finally, SecureRandom is the generator behind every session token the security article issues. The shelf was designed to compose with the rest of the course, and it will keep appearing.

Decision Framework

  1. Comparing two references that might be null? Objects.equals, never a hand-rolled condition chain.
  2. Validating a constructor or method argument? Objects.requireNonNull with the argument’s name in the message.
  3. Arithmetic where overflow would be a silent corruption? The Exact family, addExact, multiplyExact, incrementExact, and floorMod for the parity cases.
  4. Random numbers: who is the adversary? Nobody: Random, seeded in tests. Contention: ThreadLocalRandom. An attacker: SecureRandom, always.
  5. Identity: does it need coordination, or ordering? Coordination across systems: UUID. Ordering and density in one database: a long from a sequence, the Part 8 articles show that in JDBC.
  6. Writing your own helper: does the JDK have it? Check first; if not, final class, private constructor, static methods, no state.

When NOT to Use This

  • Do not build a single all-purpose Utils class. A grab bag of unrelated static methods is the package-boundary failure from the encapsulation article at file scale. Instead, one class per topic, named after it, is the honest shape.
  • Do not put behavior that needs state or configuration into a utility class. The moment a method needs a field, a dependency, or a setting, it wants to be a class with instances. Then the Part 2 service shapes are its home.
  • Do not use Math.random for integer ranges or anything adversarial. It states neither the range nor the security contract, and both of the right tools exist above.

Common Mistakes

  • Objects.hash in hashCode against a single-field equals: the pair rule from the Object article breaks. As a result, hash collections store ghosts.
  • Mixing % and floorMod assumptions: -7 % 2 is -1, so parity checks break on negatives. The fix is one prefix, Math.floorMod.
  • Expecting Math.round to return int for a double: it returns long, and the narrowing cast surprises every developer once.
  • Sharing one Random instance across threads: it works, but the contention shows up in profiles. Instead, ThreadLocalRandom exists for exactly that shape.
  • Seeding SecureRandom or Random for secrets: a seeded generator is reproducible by definition, and reproducible secrets are not secrets.
  • A utility class with a mutable static field: a global variable in disguise. Sooner or later, Part 5’s threads will find it before the review does.

Key Takeaways

  • Objects.equals is the null-safe comparison everywhere: true when both null and safe when one is. It is also uniform in every codebase that uses it.
  • Objects.requireNonNull composes validation with assignment and names the offending argument in the failure.
  • Math’s Exact family converts silent overflow into ArithmeticException, and floorMod fixes the negative-number remainder trap.
  • Three random contracts: Random for games and seeded tests, ThreadLocalRandom for contention, SecureRandom for anything an attacker might predict.
  • UUID.randomUUID gives coordination-safe version-4 identity; longs stay the right choice where ordering and density matter.
  • Your own utility classes follow one pattern: final class, private constructor, static methods, zero state.
  • Check the JDK before writing any helper; the library version has been reviewed harder than yours will be.

FAQ

What is a utility class in Java?

A final class with a private constructor and only static methods, grouping stateless helpers around one topic. Objects, Math, and Arrays are the JDK’s own examples; the private constructor makes the class impossible to instantiate.

What does Objects.requireNonNull do?

It checks that its argument is not null and returns the argument, so it composes with assignment in one line. On null it throws NullPointerException with the message you supply, which is why it is the standard constructor and setter validation.

Why does Math.addExact exist in Java?

Because plain int arithmetic wraps silently at the boundaries: Integer.MAX_VALUE plus 1 becomes negative with no exception. addExact and its Exact siblings throw ArithmeticException on overflow, converting the silent corruption into a loud failure.

Should I use Math.random or Random in Java?

Random, or ThreadLocalRandom under concurrency. Math.random returns a scaled double with no range statement and no seed control, so the range arithmetic is yours to get wrong, and reproducible tests are impossible.

How do I generate a UUID in Java?

UUID.randomUUID() for a version-4 identifier: 122 random bits, no coordination needed. Convert with toString for logs and wire formats, and keep the UUID type in fields so equality and parsing stay type-safe.

Conclusion

The JDK’s utility shelf is small enough to know completely and valuable enough to check first. It offers Objects for null-aware correctness and Math for arithmetic that refuses to lie. It also offers the three random contracts for three different adversaries, and UUID for identity that needs no counter. Your own utilities follow the same pattern the JDK models. As a result, the review question is always available: did the JDK already ship this?

Next, the following article opens the largest remaining area of the standard library: File I/O, streams, readers, and writers. That is the machinery behind every config file, export, and log the course has promised so far.

Know the shelf by name. Every helper you do not write is a helper that cannot have your bug in it.

Last updated on 22 September 2026.

Share this article

Leave a Reply

Your email address will not be published. Required fields are marked *