Fundamentals 23 min read

15 Must‑Know Java API Design Tips from Effective Java

This article distills key recommendations from Joshua Bloch’s Effective Java, covering static factory methods, builder patterns, singleton protection, memory management, and API design conventions, providing concise examples and practical guidance to help Java developers write clearer, more efficient, and safer code.

Programmer DD
Programmer DD
Programmer DD
15 Must‑Know Java API Design Tips from Effective Java

Effective Java is a must‑read Java classic; following its principles greatly improves code quality.

1. Prefer static factory methods over constructors

Examples: Integer.valueOf("1"), Boolean.valueOf("true"). Advantages: readability, performance, flexibility.

Readability

Method names convey meaning better than constructors (e.g., Point.at(x,y) vs new Point(x,y)).

Performance

Factory can reuse existing instances, such as Boolean.TRUE and Boolean.FALSE.

Flexibility

Factory can return subclasses, as in Collections utility classes.

public final class Boolean implements Serializable, Comparable<Boolean> {
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
    // constructors and factory methods...
}

2. Use a builder when a class has many constructors

In Android UI code, many overloaded constructors make usage error‑prone. A builder centralises parameter validation and improves readability.

public class AlertDialog {
    private int width;
    private int height;
    private String title;
    private String confirmText;
    private String denyText;
    private AlertDialog() {}
    protected AlertDialog(Builder b) {
        width = b.width;
        height = b.height;
        if (width == 0 || height == 0) throw new Exception("size must be set");
    }
    public static class Builder {
        private int width;
        private int height;
        private String title;
        private String confirmText;
        private String denyText;
        public Builder setTitle(String title) { this.title = title; return this; }
        // other setters …
        public AlertDialog build() { return new AlertDialog(this); }
    }
}

3. Strengthen singletons with private constructors or enum types

Private constructor plus a public static final instance prevents external instantiation; enum singleton avoids reflection and serialization attacks.

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
}
public enum Elvis {
    INSTANCE;
    // methods …
}

4. Prevent instantiation of utility classes

Declare a private constructor that throws AssertionError to avoid accidental use.

public class Util {
    private Util() { throw new AssertionError(); }
}

5. Avoid creating unnecessary objects

Reuse objects when possible.

Use object pools for expensive objects.

Avoid pools for cheap objects; modern JVM handles them efficiently.

6. Eliminate obsolete object references

Three common memory‑leak sources: self‑managed collections, caches, and listeners/callbacks. Use nulling, WeakHashMap, or proper deregistration.

public class Stack<E> {
    private Object[] elements;
    private int size = 0;
    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        Object e = elements[--size];
        elements[size] = null; // allow GC
        return e;
    }
}

7. Do not invoke garbage collection explicitly

The JVM handles GC; explicit finalizers degrade performance and portability.

8–12. Follow the general contract when overriding equals, hashCode, toString, clone, and consider implementing Comparable

Reflexive, symmetric, transitive, consistent, non‑null.

Always override hashCode when equals is overridden.

Provide informative toString.

Clone only when necessary and document behavior.

Implement Comparable for natural ordering.

13. Minimise accessibility of classes and members

Prefer private > protected > package‑private > public. Keep fields private and expose them via getters/setters.

14–15. Use accessors instead of public fields and minimise mutability

Expose state through methods; design immutable classes with defensive copies.

16. Prefer composition over inheritance

Composition reduces coupling; use it unless a true “is‑a” relationship exists.

17. Document inheritance intent or forbid it

If a class is designed for extension, provide clear documentation; otherwise declare it final.

18. Prefer interfaces to abstract classes

Interfaces define contracts without implementation baggage.

19. Use interfaces only to define types

Do not add implementation details to interfaces.

20. Class hierarchy over marker interfaces

Leverage class inheritance for shared behaviour.

21. Represent strategies with functional objects

Pass strategy objects (or lambdas) instead of creating new anonymous classes each time.

22. Prefer static nested classes when inner class does not need outer instance

Static nested classes avoid implicit reference to the outer object, improving GC.

23. Specify generic type parameters instead of raw types

Use List rather than raw List.

24. Eliminate unchecked warnings

Address compiler warnings; avoid @SuppressWarnings unless justified.

25. Return empty collections instead of null

Clients can iterate safely without null checks.

26–28. Embrace generics and bounded wildcards (PECS)

Use generic collections, prefer List over arrays, and apply extends T or super T where appropriate.

29. Use type‑safe heterogeneous containers

Prefer EnumMap/EnumSet over ordinal‑based indexing.

30. Replace int constants with enums

Enums provide type safety and readability.

31. Store enum data in fields, not ordinal()

Define constructor parameters for enum constants.

32. Use EnumSet instead of bit‑mask flags

EnumSet offers a clear, type‑safe way to handle multiple enum values.

33. Use EnumMap instead of ordinal indexing

EnumMap provides fast, type‑safe map keyed by enum.

34–36. Prefer annotations over naming conventions, and always use @Override

Annotations convey intent; @Override catches signature errors.

37. Validate method parameters

Public methods should throw exceptions for illegal arguments; private methods may use assertions.

38. Perform defensive copying for mutable inputs/outputs

public class Period {
    private final Date start;
    private final Date end;
    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end   = new Date(end.getTime());
        if (this.start.compareTo(this.end) > 0)
            throw new IllegalArgumentException(start + " after " + end);
    }
    public Date start() { return new Date(start.getTime()); }
    public Date end()   { return new Date(end.getTime()); }
}

39–42. Design method signatures carefully, avoid overloading, varargs, and returning null

Clear signatures improve readability; use varargs sparingly; prefer empty arrays/collections.

43. Document all exported API elements with Javadoc

Provide concise, behavior‑focused comments.

44–45. Keep variable scope minimal and prefer foreach loops

Limit visibility and use enhanced for‑each for safety.

46. Avoid float and double for precise calculations

Use BigDecimal or scaled integers for monetary values.

47. Prefer primitive types over boxed types

Primitives avoid nulls and are more efficient.

48–49. Do not use strings as substitutes for other types or for heavy concatenation

Use proper types; employ StringBuilder for large concatenations.

50–51. Depend on interfaces rather than reflection

Reflection loses compile‑time safety and incurs performance costs.

52. Use JNI only when absolutely necessary

Native code adds complexity and portability issues.

53. Optimize only after profiling

Premature optimization often harms maintainability.

54–57. Follow naming conventions, use exceptions appropriately, and avoid unnecessary checked exceptions

Consistent naming, throw exceptions only for exceptional conditions, and prefer unchecked exceptions when recovery is not expected.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaprogrammingbest practicesapi-designEffective Java
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.