Table of contents


Item 49. Check parameters for validity

Check parameters for validity

Validate parameters before the method body starts and document the constraints. Skipping validation leads to ambiguous failures, incorrect results, or delayed failures when another object mutates state.

public and protected methods should document the exceptions they throw for invalid parameters. Class-level comments apply to all public methods and keep documentation cleaner. You can use annotations like @Nullable, but they are not standard. Constructor parameter validation is also required to prevent creation of objects that violate class invariants.

Validation techniques

The requireNonNull method added in Java 7 makes null checks more flexible.

public void someMethod(Integer val) {
    Integer integer = Objects.requireNonNull(val, "Parameter is null");
    System.out.println(integer);
}

Passing null produces this error:

Exception in thread "main" java.lang.NullPointerException: Parameter is null
	at java.base/java.util.Objects.requireNonNull(Objects.java:246)

Java 9 also adds range-checking utilities in Objects: checkFromIndexSize, checkFromToIndex, and checkIndex. They are less flexible than null checks. You cannot specify a custom message and they target lists and arrays.

List<String> list = List.of("a", "b", "c");

// Exception in thread "main" java.lang.IndexOutOfBoundsException: 
//      Index 4 out of bounds for length 3
Objects.checkIndex(4, list.size());

If a method is private and not exposed, the developer controls all call sites. In that case, you can validate parameters with assert. To enable assertions, pass -ea or --enableassertions in VM options (IntelliJ). Without it, assertions are ignored. If the condition is false, Java throws an AssertionError.

private void someMethod(int arr[], int length) {
    assert arr != null;
    assert length >= 0 && arr.length == length;

    // do something
}

When validation is unnecessary

In some cases, explicit validation is not required. If validation is too expensive or the computation implicitly validates, you can skip it. For example, Collections.sort(List) compares all elements. If an element is not comparable, ClassCastException occurs. Pre-validating all elements adds no value.

Validate parameters at the beginning of the method body.



Item 50. Make defensive copies when needed

Make defensive copies when needed

Java is safe, but you must assume clients can break invariants. If a class accepts or returns a mutable component, make a defensive copy.

Create defensive copies when the situation calls for it.



Item 51. Design method signatures carefully

Design method signatures carefully

Name methods carefully. Follow standard naming conventions and avoid overly long names. When in doubt, refer to the Java library guide and keep names consistent with others in the same package.

Avoid too many convenience methods. Each method requires documentation, maintenance, and tests. Keep parameter lists short; four or fewer is a good target. Multiple parameters of the same type in a row are especially problematic. Here are ways to reduce parameters.

Split the operation into multiple methods. Suppose you want to find an element in a list using the start, end, and target element. That is three parameters. The List interface provides subList and indexOf as separate methods. Combine them instead.

List<String> list = Lists.of("a", "b", "c", "d");

List<String> newList = list.subList(1, 3);
int index = newList.indexOf("b"); // 0

Another approach is a helper class that groups parameters.

// Existing method
public void someMethod(String name, String address, String email, String job) {
    // do something
}

// Helper class
class SomeHelper {
    String name;
    String address;
    String email;
    String job;
}

public void someMethod(SomeHelper someHelper) {
    // do something
}

You can also use the builder pattern.

Prefer interfaces to classes as parameter types. Use Map instead of HashMap. A concrete class forces clients into a specific implementation.

Prefer a two-element enum to a boolean, unless the boolean is unambiguous. For example:

public void setProgram(boolean isNews) {
    if (isNews) {
        // set program for news
    } else {
        // set anything
    }
}

If you add a new program type beyond news, a boolean no longer scales. With an enum, the code stays clean.

public enum ProgramType { NEWS, SPORTS, ENTERTAINMENT }

public void setProgram(ProgramType type) {
    switch (type) {
        case NEWS:
            // do something
            break;
        case SPORTS:
            // do something
            break;
        case ENTERTAINMENT:
            // do something
            break;
    }
}
Design method names and parameter lists with care.



Item 52. Use overloading judiciously

Use overloading judiciously

Overridden methods are selected dynamically at runtime, while overloaded methods are selected at compile time. That distinction often leads to surprising behavior.

class ColectionClassifier {
    public static String classify(Set<?> set) {
        return "Set";
    }

    public static String classify(List<?> list) {
        return "List";
    }

    public static String classify(Collection<?> collection) {
        return "Other"
    }

    public static void main(String[] args) {
        Collection<?>[] collections = {
            new HashSet<String>(),
            new ArrayList<Integer>(),
            new HashMap<String, String>().values()
        };

        for (Collection<?> c : collections) {
            System.out.println(classfy(c));
        }
    }
}

This prints β€œOther” three times. At compile time, c is always Collection<?>. To avoid confusion, avoid overloads with the same number of parameters. Overloading varargs methods is especially dangerous.

Instead of overloading, you can use distinct method names. ObjectOutputStream takes this approach with writeBoolean(boolean) and writeInt(int). Its counterpart ObjectInputStream pairs them with readBoolean() and readInt().

Constructors cannot change names, so constructor overloads are inevitable. Use static factories as an alternative when possible.

Avoid overloading when parameter counts are the same.



Item 53. Use varargs judiciously

Use varargs judiciously

When you call a varargs method, Java creates an array sized to the number of arguments and copies them into it. If at least one argument is required, add a required parameter before the varargs parameter.

static int min(int firstArg, int... remainingArgs) {
    int min = firstArg;
    for (int arg : remainingArgs) {
        if (arg < min) {
            min = arg;
        }
    }
    return min;
}

Varargs can hurt performance because each call allocates and initializes an array. You can use fixed-arity overloads for the most common cases and fall back to varargs for the rest.

public void foo() {}
public void foo(int arg1) {}
public void foo(int arg1, arg2) {}
public void foo(int arg1, arg2, arg3) {}
public void foo(int arg1, arg2, arg3, int... restArg) {}

The book assumes 95% of method calls use three or fewer arguments, so varargs handles the remaining 5%.

Varargs can be expensive. Use them carefully.



Item 54. Return empty collections or arrays, not nulls

Return empty collections or arrays, not nulls

You often see methods return null for empty collections.

private final List<Cheese> cheesesInStock = ...;

public List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
}

This forces clients to add defensive null checks. The performance difference between null and an empty container is negligible, so return an empty container instead.

public List<Cheese> getCheeses() {
    return new ArrayList<>(cheesesInStock);
}

Depending on usage patterns, allocating empty collections might still be costly. In that case, return a shared immutable empty collection.

public List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheesesInStock);
}

The same applies to arrays. Return a zero-length array instead of null. You can also avoid repeated allocations by reusing a shared empty array.

// Return a zero-length array
public Cheese[] getCheeses() {
    return cheesesInStock.toArray(new Cheese[0]);
}

// Reuse a shared array
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];


public Cheese[] getCheeses() {
    return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
    // Preallocating like this can hurt performance.
    // return cheesesInStock.toArray(new Cheese[cheesesInStock.size()]);
}
Return empty arrays or collections, not null.



Item 55. Return optionals judiciously

Return optionals judiciously

Sometimes a method cannot return a value. Before Java 8, you either threw an exception or returned null. Exceptions should represent exceptional cases only, and null introduces NullPointerException risk and extra null-handling code.

Java 8 adds Optional<T>, which holds either a non-null T reference or nothing. It is an immutable container of at most one element and helps you write null-safe code.

// Without Optional
public static <E extends Comparable<E>> E max(Collection<E> c) {
    if (c.isEmpty()) {
        throw new IllegalArgumentException("Empty collection");
    }        

    E result = null;
    for (E e : c) {
        if (result == null || e.compareTo(result) > 0)
            result = Objects.requireNonNull(e);
    }
    return result;
}

// With Optional and streams
public static <E extends Comparable<E>>
        Optional<E> max(Collection<E> c) {
    return c.stream().max(Comparator.naturalOrder());
}

Using Optional

Provide a default value

If the method returns no value, you can supply a default.

String lastWordInLexicon = max(words).orElse("No word...");

Throw a specific exception

If no value exists, throw the exception you want. Here we pass an exception factory, so the exception is created only when needed.

Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);

Assume a value is present

Use this only when you are sure a value exists. Otherwise NoSuchElementException occurs.

Element lastNobleGas = max(Elements.NOBLE_GASES).get();

When the default is expensive

If creating the default is expensive, use orElseGet. It creates the value lazily via Supplier<T>.

Connection conn = getConnection(dataSource).orElseGet(() -> getLocalConn());

Pitfalls

Do not wrap containers such as collections, streams, arrays, or optionals inside an Optional. Returning Optional<List<T>> is worse than returning an empty List<T> because clients must handle an extra optional layer.

Also avoid using Optional as a map key, map value, collection element, or array element. It creates ambiguity. For example, a map can represent β€œno key” and β€œkey mapped to an empty optional,” which are different states.

For performance-critical methods, returning null or throwing an exception can be better.



Item 56. Write doc comments for all exposed API elements

Write doc comments for all exposed API elements

Good documentation improves API usability. Java’s Javadoc collects document comments from source files and generates API docs.

To document correctly, add doc comments to all public classes, interfaces, methods, and field declarations. Method doc comments should describe the contract between the method and the client. For non-inheritance usage, focus on what the method does, not how it works.

Writing guide

Tag Usage Guidance
@param every parameter use a noun phrase without a period.
@return non-void return use a noun phrase without a period.
Omit when it repeats the method description.
@throws all possible exceptions use a noun phrase without a period.
@code render in code font ignores HTML elements and other Javadoc tags.
@implSpec implementation spec describes the contract between the method and subclasses.
@literal ignore HTML unlike @code, it does not render in code font.
@index indexing added in Java 9; indexes the specified term.
@summary summary summary of the description (since Java 10).

General guidance

  • Prioritize readability.
    • {@literal |r| < 1} vs. |r| {@literal < } 1
    • Only < needs @literal, but you can wrap a whole phrase for readability.
  • Write package documentation in package-info.java.
  • Write module documentation in module-info.java.
  • For generics, document every type parameter.
  • For enums, document each constant.
  • For annotations, document each member.
  • For classes or static methods, include thread-safety level.
  • For serializable classes, document the serialization form.
  • Method docs can be inherited; without a comment, Javadoc pulls from supertypes.
    • Interface docs take precedence over class docs.
Doc comments are the best way to document APIs.