Course content
Builder: Refactor the EmailMessage Constructor
Medium·Tagsdesign-patternsbuilderimmutabilityrefactoring
Problem Statement
The starter `EmailMessage` is constructed through a single 9-parameter public constructor. Call sites pass `null` for optional collections, line up booleans positionally, and become unreadable as soon as a couple of optional flags are involved:
```java
// Required: to, from, subject, body. Optional: cc, bcc, attachments, important, readReceipt.
new EmailMessage("a@x.com", "b@x.com", "Hi", "Hello",
List.of("c@x.com"), null, null, true, false);
```
Which boolean is `important` and which is `readReceiptRequested`? Why is one collection a list and the other two `null`? The constructor signature is the only documentation, and adding a tenth field means breaking every existing caller.
Refactor `EmailMessage` into the Builder pattern so the call site becomes self-documenting:
```java
EmailMessage email = EmailMessage.builder()
.to("a@x.com")
.from("b@x.com")
.subject("Hi")
.body("Hello")
.cc(List.of("c@x.com"))
.important(true)
.build();
```
After your refactor the design must expose this exact public API:
```java
class EmailMessage {
public static EmailMessage.Builder builder();
public String getTo();
public String getFrom();
public String getSubject();
public String getBody(); // default "" when not set
public List<String> getCc(); // default empty, immutable
public List<String> getBcc(); // default empty, immutable
public List<String> getAttachments(); // default empty, immutable
public boolean isImportant(); // default false
public boolean isReadReceiptRequested();// default false
// No public constructor. Builder.build() is the only way to construct.
public static class Builder {
public Builder to(String to);
public Builder from(String from);
public Builder subject(String subject);
public Builder body(String body);
public Builder cc(List<String> cc);
public Builder bcc(List<String> bcc);
public Builder attachments(List<String> attachments);
public Builder important(boolean important);
public Builder readReceiptRequested(boolean readReceiptRequested);
public EmailMessage build(); // throws IllegalStateException when required fields are missing
}
}
```
The validator runs four bundled checks that cover the design contract:
1. build_and_chain — required-only `builder().to(t).from(f).subject(s).build()` returns an EmailMessage with the given to/from/subject and the documented defaults (body `""`, cc/bcc/attachments empty lists, both booleans `false`); building with every optional field set returns those values; the fluent chain returns the same `Builder` instance throughout (so every setter must be `return this`).
2. missing_required_throws — `build()` without `to` (or `from`, or `subject`) throws `IllegalStateException`.
3. structural — `EmailMessage` declares no public constructor and no public `setXxx` methods. The only construction path is `Builder.build()`; the built object is structurally immutable.
4. immutability_and_defensive_copy — `getCc()`/`getBcc()`/`getAttachments()` return collections that throw `UnsupportedOperationException` on mutation, AND mutating the original list passed to `builder.cc(...)` AFTER `build()` returns does not change the built EmailMessage's cc list (Builder must defensively copy on the way in).
The smelly starter has a public 9-arg constructor and a stub `Builder` whose methods all throw `UnsupportedOperationException`, so it fails every behavioural check until the refactor is real.
Starter code and validators are available in Java, Python, and C++.
Examples
Example 1
Input
EmailMessage.builder().to("a@x.com").from("b@x.com").subject("Hi").build()Output
"EmailMessage{to=a@x.com, from=b@x.com, subject=Hi, body=\"\", cc=[], bcc=[], attachments=[], important=false, readReceiptRequested=false}"Why
Optional fields fall back to documented defaults. body defaults to empty string; the three lists default to empty immutable lists; both booleans default to false.
Example 2
Input
EmailMessage.builder().to("a").from("b").subject("s").cc(List.of("c", "d")).important(true).build()Output
"Email with cc=[c, d] and important=true; remaining optionals at defaults."Why
Each fluent setter overrides one field. The chain is self-documenting at the call site.
Example 3
Input
EmailMessage.builder().from("b").subject("s").build()Output
"throws IllegalStateException(\"to is required\")"Why
build() validates required fields. The exception names the missing field so the caller can fix it without reading the implementation.
Constraints
- •EmailMessage must declare zero public constructors. The only way to construct it is EmailMessage.builder().build().
- •EmailMessage.builder() must return an EmailMessage.Builder instance.
- •Every fluent setter on Builder must return the same Builder instance so chaining works.
- •Builder.build() must throw IllegalStateException when any of to, from, or subject is missing.
- •EmailMessage's getCc(), getBcc(), getAttachments() must return immutable collections (mutation throws UnsupportedOperationException).
- •Mutating a list AFTER passing it to builder.cc(...)/bcc(...)/attachments(...) must not change the built EmailMessage's collections — defensive copy is required.
- •EmailMessage must declare no public setter methods (no setXxx). All fields are private final.
Hints
Stuck? Reveal a nudge toward the right pattern, one step at a time.
Hint 1
Make the EmailMessage(...) constructor private. Builder.build() will be the only place that calls it.
Hint 2
Inside Builder, default optional fields where they're declared: `private String body = "";`, `private List<String> cc = new ArrayList<>();` etc. That way, callers who don't set them get the defaults for free.
Hint 3
Each fluent setter should be three lines: assign the field, return `this`. For collections, defensively copy on the way in: `this.cc = new ArrayList<>(cc);` so the caller's later mutations don't leak into the built object.
Hint 4
build() must validate to/from/subject and throw `IllegalStateException` (not `IllegalArgumentException`) with a clear message naming the missing field.
Hint 5
Inside the new private constructor, finalise the immutability: `this.cc = List.copyOf(b.cc);` produces a list that throws on mutation. Same for bcc and attachments. The getters then just return the field — no extra copying needed at read time.