🏛️ Design Principles & Patterns · Advanced

Factory & static factory methods in Java

Named constructors, hiding implementation classes.

🧩 The mysteryInteger.valueOf(100) == Integer.valueOf(100) is true. Integer.valueOf(1000) == Integer.valueOf(1000) is false. A static factory is behind that riddle.

Constructors with names

A static factory method is a named static method that returns an instance: List.of(...), Duration.ofSeconds(5), Optional.empty(). The name shows intent, which new can't do. Common names: of, from, valueOf, getInstance, newInstance.

var wait = Duration.ofSeconds(5);
var xs = List.of(1, 2, 3);
Optional<String> none = Optional.empty();

Superpower: reuse instances

A constructor must create a new object. A factory may return an existing one. Boolean.valueOf returns the shared TRUE/FALSE, and Integer.valueOf caches values from -128 to 127. Your own types can do the same.

static Money of(long cents) {
    if (cents == 0) return ZERO; // cached
    return new Money(cents);
}
🔮 Predict it

Right at the edge

What does this print?

Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
Integer c = Integer.valueOf(128);
Integer d = Integer.valueOf(128);
System.out.println(a == b);
System.out.println(c == d);
  1. true false
  2. true true
  3. false false
Show the answer

127 is inside the cache, so a and b are the same object. 128 is just outside the default cache, so c and d are two different objects and == compares references. Lesson: compare values with equals, never ==.

Superpower: hide the real class

A factory can return any subtype, even a private one. Interfaces can't declare constructors, but they can have static methods. Users depend only on Cache, so you can swap the implementation in a future release without breaking anyone. List.of does exactly this.

interface Cache {
    Object get(String key);
    static Cache lru(int size) {
        return new LruCache(size); // hidden
    }
}
🔮 Predict it

The hidden class strikes back

List.of returns a hidden implementation class. What happens here?

List<String> xs = List.of("a", "b");
xs.set(0, "z");
System.out.println(xs);
  1. [z, b]
  2. Throws UnsupportedOperationException
  3. Compile error
Show the answer

The declared type is plain List, so set compiles. But the hidden class behind List.of is unmodifiable, so set, add and remove all throw at runtime.

⚠️ The trap

The price of factories

If a class offers only static factories and private constructors, it can't be subclassed. Effective Java lists that as a drawback, not an advantage (though it nudges you toward composition). Factories are also a bit harder to spot in docs than constructors.

💼 In the real world

API design

The JDK is full of factories: Path.of, List.copyOf, HttpClient.newHttpClient(). Library authors use them to return interfaces and keep the freedom to change implementation classes later. Asking users to call new LruCacheImpl(100) locks you in forever.

Key takeaways

  1. Names show intent: Duration.ofSeconds(5)
  2. Can reuse instances: Integer.valueOf caches -128 to 127
  3. Can return a hidden subtype: List.of returns a private class
  4. Common names: of, from, valueOf, getInstance, newInstance

💡 You order 'a latte' at the counter; you don't build the espresso machine yourself.

🤯 Did you know?

The Integer(int) constructor was deprecated in Java 9 and marked for removal in Java 16. The JDK itself wants you to call Integer.valueOf instead.

Practice questions

What does this print?

Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
Integer c = Integer.valueOf(1000);
Integer d = Integer.valueOf(1000);
System.out.println(a == b);
System.out.println(c == d);
  1. true true
  2. false false
  3. false true
  4. true false
Check your answer

true false. Integer.valueOf returns cached objects for -128 to 127, so a and b are the same object. 1000 is outside the default cache, so c and d are different objects.

What does this print?

List<Integer> xs = List.of(1, 2, 3);
xs.add(4);
System.out.println(xs);
  1. [1, 2, 3, 4]
  2. Throws UnsupportedOperationException
  3. [1, 2, 3]
  4. Compile error
Check your answer

Throws UnsupportedOperationException. List.of is a static factory that returns a hidden unmodifiable implementation. The declared type is just List, so add compiles but fails at runtime.

Next: new Pizza(12, true, false, true). Quick: which boolean is the cheese? Builders fix this.