Null Handling on Composed Fields
Before You Start
Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.
Not sure? Take the 60-second self-check.
1. Default null. A reference-type field is declared but not initialized. What value does it hold?
Check
null. Java’s default for uninitialized reference fields is null (JLS §4.12.5).
2. NPE cause. When does a NullPointerException occur?
Check
When code tries to dereference a null reference: calling a method on it (null.method()), accessing a field on it, or using it in a context that requires a non-null object.
3. Fail-fast principle. Why is it better to throw an exception at the constructor than to let a null field cause an NPE later?
Check
Throwing at the constructor identifies the exact source of bad input immediately. An NPE that occurs later, inside a method that dereferences the null field, produces a stack trace pointing to the wrong place, making the bug harder to diagnose.
Try This First
// Person constructor with NO precondition check:
public Person(final String fn, final String ln, final Color color) {
this.fn = fn; this.ln = ln; this.color = color;
}
Person p = new Person("a", "b", null);
Arrays.sort(people); // people contains p
Before reading: where does the first exception occur, and what kind of exception is it?
Check
Inside Person.compareTo, when it calls this.color.compareTo(another.color). The field this.color is null, so null.compareTo(...) throws NullPointerException. The stack trace points inside compareTo, not at new Person(...) where the bad data entered.
What You Need To Walk In With
The Insight: A null-check in the constructor is a fail-fast guard. It stops bad data at the entry point, produces a diagnostic message naming the class and method, and prevents null from silently propagating to where it causes an NPE whose stack trace points to unrelated code. Javadoc that says “@throws IAE if any parameter is null” without a matching body check is a lie: the class is not actually null-safe.
After working through this, you can: write a Steiner-standard constructor precondition check for any set of reference parameters; predict exactly which exception is thrown and where when the check is absent; explain why a Javadoc-only promise is insufficient; and distinguish the correct fix (reject at entry) from the wrong one (catch the downstream NPE).
How It Works
The Steiner-standard null check
public Person(final String fn, final String ln, final Color color)
{
if (fn == null || ln == null || color == null)
throw new IllegalArgumentException("Bad parameter(s) in Person EVC");
this.fn = fn;
this.ln = ln;
this.color = color;
}
Requirements:
- Parameters are
final. - Check comes BEFORE any assignment.
- The message names the class and method:
"Bad parameter(s) in Person EVC"(EVC = Essential Value Constructor). - The check uses
||so that any null triggers the exception.
For String fields, the standard may also check .isEmpty():
if (fn == null || fn.isEmpty() || ln == null || ln.isEmpty() || color == null)
An empty string is often as invalid as a null string for domain objects.
What happens without the check
// S20 Lab 2 Person constructor (as shipped, without full body enforcement)
public Person(final String fn, final String ln, final Color color) {
this.fn = fn; this.ln = ln; this.color = color;
}
Person p = new Person("a", "b", null);
// p.color == null; p is in invalid state
Arrays.sort(people); // triggers compareTo
// Inside compareTo: this.color.compareTo(another.color) -> NPE
// Stack trace: at Person.compareTo(Person.java:55)
// True source: the null was allowed in at construction
The NPE’s stack trace points to compareTo, not to new Person(...). The true source is buried.
Why the body check must match the Javadoc
S20.Lab2.Person.java Javadoc says:
@throws IllegalArgumentException if any of the parameters are null
But the as-shipped body (in some versions) lacks the explicit check. The Javadoc makes a promise the code does not keep. A test that passes null and expects IAE will instead see a delayed NPE from compareTo. Matching the Javadoc with actual code is a standard professional obligation.
Worked Example: Predict Then Check
// Constructor A: with check
public Book(final String title, final Publisher pub) {
if (title == null || pub == null)
throw new IllegalArgumentException("Bad parameter(s) in Book EVC");
this.title = title;
this.pub = pub;
}
// Constructor B: without check
public Book(final String title, final Publisher pub) {
this.title = title;
this.pub = pub;
}
Which constructor throws, and at what line, when called with new Book("Java", null)?
Reasoning
Constructor A: throws IllegalArgumentException at the if line, immediately on construction.
Constructor B: does not throw; stores null in this.pub. The NPE occurs later when some method calls this.pub.getPubName() or similar.
Show answer
Constructor A throws IllegalArgumentException at construction time with the message "Bad parameter(s) in Book EVC".
Constructor B succeeds silently and creates an invalid Book with pub == null. The NPE occurs later, in whatever method first calls this.pub.someMethod(), producing a stack trace that does not mention the constructor.
Quick check
Check your understanding
In the Steiner-standard constructor, the null check must appear at what position relative to the field assignments?
Common Misconceptions
Misconception 1: Javadoc promises are sufficient without body enforcement
Wrong mental model: “I documented
@throws IAE if null; the class is null-safe.”
Why it breaks: Javadoc is a contract specification, not enforcement. If the body lacks the check, null can enter and propagate. A test that passes null expecting IAE will see a downstream NPE instead. The contract is unfulfilled.
How to correct: Match every @throws promise with an explicit if (x == null) throw new IllegalArgumentException(...) in the body. Code and documentation must agree.
Source: S20.Lab2.Person.java (Javadoc vs. body mismatch).
Quick check
Check your understanding
A constructor’s Javadoc says ‘@throws IllegalArgumentException if any parameter is null’, but the body has no null check. A caller passes null. What actually happens?
Misconception 2: catching the downstream NPE is the right fix
Wrong mental model: “The NPE happens in
compareTo; I will wrap it in try/catch.”
Why it breaks: Catching an NPE that results from a null field masks the real bug. The null was allowed into the object at construction. The fix is to reject it there. Code that catches NPEs on field access is hiding a design error, not correcting it. Every future path through the code that touches the null field is now a potential NPE site.
How to correct: Reject null at the constructor. Every method in the class can then assume all fields are non-null and read directly, with no defensive null checks scattered through the methods.
Source: Bloch, Effective Java, Item 72 (favor the use of standard exceptions); fail-fast principle.
Formal Definition and Interface Contract
The Steiner coding standard (per course documentation):
Constructors must validate all reference parameters. A null reference parameter must result in
IllegalArgumentExceptionwith a message of the form"Bad <ParameterType> in <ClassName>.<method>".
Bloch, Effective Java, Item 49 (check parameters for validity):
You should check the validity of parameters for public and protected methods and should throw appropriate exceptions when invalid values are detected. [...]
NullPointerExceptionis not the right exception when the problem is thatnullwas passed to a method that does not allow it; the right exception isIllegalArgumentException.
Mental Model
A null check in the constructor is a security checkpoint at the building entrance. Anything that should not be there is rejected at the door. Without the checkpoint, the contraband (null) enters, wanders through the building, and causes a problem somewhere unrelated to the entrance, making the investigation hard. The checkpoint’s rejection message names exactly where the bad input was found.
Connections
Within CSCD 210/211: The precondition check pattern (IAE with class-and-method message) is the same one used in comparator classes. Every compare method that throws IAE on null follows the same standard.
Looking back: Declaring and Initializing a Class-Type Field established that fields must be initialized. This lesson establishes how to prevent the initialization value from being null when null is not valid.
Looking ahead: The next set of lessons covers defensive copying of mutable fields (arrays, ArrayList), which handles the same fail-fast philosophy for the contents of mutable containers.
Go Deeper (optional)
None of what follows is needed to write correct, professional-grade Java code. Read it when you are curious, not because you feel you must.
The explicit if (x == null) throw new IllegalArgumentException(...) form used in labs is deliberately low-level so that the mechanism stays visible while you are learning it. Modern Java (Java 17 and later) provides Objects.requireNonNull(x, "message") as a one-line equivalent that reads cleanly in production code and is idiomatic in real codebases. Both approaches enforce exactly the same contract; the explicit form is used here because seeing every moving part once is the point.
Null safety is one of the most common topics in Java code reviews on professional teams. A constructor that fails fast at the entry point is easier to test, easier to debug, and easier to trust than one that scatters null checks across every method. Knowing why the check belongs at construction (not inside compareTo, not inside getPubName) is a specific, demonstrable skill that comes up in internship and entry-level interviews.
For those who want to see how this idea generalizes: the fail-fast precondition is an instance of the principle of checking invariants at the boundary of a module, a design idea traced to David Parnas’s 1972 paper on information hiding. The same principle recurs in type theory as the distinction between nullable and non-nullable types: languages such as Kotlin and modern Swift enforce at compile time what Java forces you to enforce at runtime, making null-safety a property the compiler can prove rather than something you trust a constructor to do. In design-pattern terms, the choice between a constructor that rejects bad data and one that accepts any input determines how complex every downstream method must be; rejecting at the boundary is what allows every later method to skip redundant checks entirely.
Practice
Level 1
Add the Steiner-standard null check to this constructor:
public class Team {
private String name;
private Player captain;
public Team(final String name, final Player captain) {
this.name = name;
this.captain = captain;
}
}
Show answer
public Team(final String name, final Player captain)
{
if (name == null || name.isEmpty() || captain == null)
throw new IllegalArgumentException("Bad parameter(s) in Team EVC");
this.name = name;
this.captain = captain;
}
Level 2
S20 Lab 2’s Person constructor Javadoc says “@throws IAE if any parameters are null,” but the body does not include the check. Describe: (a) the behavior when new Person("a", "b", null) is called, and (b) what test expectation fails.
Show answer
(a) Without the check, new Person("a", "b", null) succeeds. this.color is set to null. The Person object is in an invalid state with no immediate indication.
(b) A test that calls new Person("a", "b", null) and expects IllegalArgumentException to be thrown will instead see no exception from the constructor. The test fails because the Javadoc promise is not kept. Later, when the person is sorted and compareTo dereferences this.color, NullPointerException is thrown from inside compareTo, not from the constructor, confusing the test further.
Source: S20.Lab2.Person.java.
Level 3
Write the complete constructor for a Book class with fields String title, String isbn (both must be non-null and non-empty), Genre type (enum, non-null), Publisher pub (non-null), int pages (must be >= 1). Follow Steiner standards exactly.
Show answer
public Book(final String title, final String isbn, final Genre type,
final Publisher pub, final int pages)
{
if (title == null || title.isEmpty() ||
isbn == null || isbn.isEmpty() ||
type == null || pub == null || pages < 1)
throw new IllegalArgumentException("Invalid parameter(s) in Book EVC");
this.title = title;
this.isbn = isbn;
this.type = type;
this.pub = new Publisher(pub.getPubName(), pub.getPubCity()); // defensive copy for mutable type
this.pages = pages;
}
Pattern from S20.Lab3.Book.java.
Check Yourself
Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
Which statement correctly describes the Steiner-standard placement of a null check in a constructor?
A Person constructor stores null in this.color without a null check. What exception is thrown and where, when Arrays.sort(people) runs?
Trace this code and state what is printed or thrown:
``java
public Book(final String title, final Publisher pub) {
if (title == null || pub == null)
throw new IllegalArgumentException("Bad parameter(s) in Book EVC");
this.title = title;
this.pub = pub;
}
Book b = new Book("Java", null);
System.out.println("Created: " + b.title);
``
S20 Lab 2 Person’s Javadoc says ‘@throws IllegalArgumentException if any parameter is null’, but the constructor body has no null check. A test passes null as the color and expects IllegalArgumentException. What does the test observe?
Which repair correctly addresses the root cause when a null composed field causes a NullPointerException inside compareTo?