Exceptions in Java Constructors
A canonical constructor takes all required parameters and carries the validation logic, such as the firstName null check in the source.
Predicting What a Null First Name Does
Imagine a FullName class whose constructor is supposed to accept a first name and a last name. A caller writes new FullName(null, "Lovelace"). Will a FullName object come into existence, or will something stop the creation? This article answers that question by following the path the program takes, step by step, from the call to the outcome.
What do you think happens?
Given a constructor that rejects a null firstName, what happens when someone calls new FullName(null, "Lovelace")?
Reveal answer
The constructor throws an IllegalArgumentException with the message "First name can't be null." and no object is returned to the caller.
The guard in the canonical constructor checks firstName. When the value is null, the constructor throws instead of finishing normally, so the caller never receives a usable FullName object. Write down your own guess first, then compare it with the trace in the next sections.
Tracing the Canonical Constructor's Guard
A canonical constructor takes all of the required parameters and carries the validation logic for the class. In the FullName example from the source, the canonical constructor's body is a single check on firstName. It contains no assignments of its own. That single statement is the only thing standing between a set of arguments and an accepted object.
- The compact canonical constructor has no parameter list; the record's components are supplied implicitly.
- The only statement in the body is the
firstNamecheck. - The one-argument constructor is an overload that delegates to the canonical form.
The check works as a gate. When firstName is null, the constructor throws an IllegalArgumentException whose message is "First name can't be null." Throwing is the mechanism that stops object creation. Because the constructor does not finish normally, the caller does not get a usable object back. Values that fail the check never become part of a FullName object.
In a constructor, the decision about whether an object is acceptable is made by the code in the constructor body, not by the caller. The caller can only supply arguments and observe the result.
How a One-Argument Constructor Delegates
The second constructor in the lesson takes fewer arguments than the canonical one. It takes only onlyName and hands the rest of the work to the canonical constructor using this(...). The call this(onlyName, null) does not create a second object. It invokes the canonical constructor, passing the caller's onlyName as the first argument and null as the second.
Because the canonical constructor runs the firstName check, every path that goes through this call still passes through that check. The overloaded constructor gives callers a shorter way to write the call, while the validation logic stays in one place. This is the design benefit: if the rule about null first names ever changes, only the canonical constructor needs editing.
Read the diagram from the top. The one-argument constructor hands control to the canonical constructor through the this call. The canonical constructor then runs its guard on firstName. Only the canonical constructor decides whether the object is accepted. The path to the throw and the path to a created object both start at the same place, the canonical guard, no matter which overloaded constructor the caller used.
Order of Execution When new Is Called
To trace object creation, follow the constructor calls in order and find the guard that actually decides the outcome. For new FullName("Ada"), the caller's call goes first to the one-argument constructor. That constructor's only statement is this(onlyName, null), so control moves to the canonical constructor before anything else happens in the one-argument constructor. The canonical guard runs against firstName, which here is "Ada", so the check passes.
The sequence shows that the canonical constructor's body runs to completion, or throws, before control returns to the one-argument constructor. The canonical constructor is therefore the first constructor body whose result matters for acceptance. If it throws, nothing after the this(...) call in the caller's constructor gets a chance to finish normally.
Object State After a Failed Creation
When the canonical constructor throws, no object is returned to the caller. The variable on the left side of the assignment does not receive a FullName object. Consider a trace of new FullName(null, "Lovelace"), where each step is shown with the state it produces.
Tracing a creation that fails the guard
Trace what happens to firstName, lastName, and the caller's variable when new FullName(null, "Lovelace") runs.
- 1Call the canonical constructor
Two arguments are supplied, so the canonical constructor runs directly.
firstNameis null andlastNameis Lovelace. - 2Evaluate the guard
The check
firstName== null is true, so the if-branch is taken. - 3Throw the exception
An
IllegalArgumentExceptionwith the message First name can't be null. is thrown. The constructor does not finish normally. - 4Inspect the caller's variable
No FullName object was returned, so the variable receives no usable object. Any code that tries to use it after the failed assignment never runs.
The object is not created. The exception stops the assignment, and the caller must handle the exception or let it propagate.
Mistakes When Reading Constructor Chains
Common mistake Assuming the one-argument constructor performs the validation itself
What it looks likeLooking only at public FullName(String onlyName) and concluding that a null value is handled there.
Why it failsThe one-argument constructor only calls
this(onlyName, null). The guard lives in the canonical constructor.Do this insteadFollow the this(...) call to the canonical constructor and read its body.
Common mistake Thinking that the new expression returns an object even when a constructor throws
What it looks likeExpecting a FullName variable to exist after new FullName(null, "Lovelace") throws.
Why it failsThe constructor does not finish normally, so no usable object is returned to the caller.
Do this insteadExpect either a fully created object or an exception, and handle the exception where it matters.
Common mistake Believing the overloaded constructor creates a second object
Why it failsthis(onlyName, null)invokes the canonical constructor on the same object being created; it does not create another one.Common mistake Expecting the caller to decide whether the object is acceptable
Why it failsIn a constructor, acceptance is decided by the code in the constructor body, not by the code that calls new.
Where to Look First When Results Differ
When an object-creation result differs from what you expected, whether an unexpected field value or an unexpected exception, the most likely place to look is the constructor in the delegation chain whose guard decides the outcome. In the FullName example, that is the canonical constructor. Check the overloaded constructor only to confirm which arguments it passes along.
- 1Identify which constructor the new expression actually matched, including any overloaded constructor that was called.
- 2Follow each
this(...)call to the constructor it invokes, noting the exact arguments passed. - 3Find the guard in the canonical constructor body that decides whether the object is accepted.
- 4Check the values that reach that guard, since the outcome depends on them rather than on the caller's intent.
Practice Tracing a Constructor Chain
Using the FullName class above, trace what happens for new FullName("Grace") and for new FullName(null). For each call, state which constructor's body decides the outcome, whether an object is returned, and what the caller's variable holds afterward.
Hints
- Start with the one-argument constructor and follow its
this(...)call. - The guard is in the canonical constructor. Ask what
firstNameholds in each case. - A thrown exception means no object is returned to the caller.
Key Takeaways
- A canonical constructor carries the validation logic; in the FullName example, its
firstNamenull check decides whether the object is accepted. - When the guard fails, the constructor throws an
IllegalArgumentException, and no object is returned to the caller. - An overloaded constructor delegates to the canonical constructor with
this(...), so the validation stays in one place. - To trace creation, follow the constructor calls in order and find the guard that actually decides the outcome.
- When a creation result differs from expectations, check the canonical constructor's guard and the arguments that reach it first.