Concepts / Exceptions in Java Constructors

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.

  • Programming

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")?

  • A FullName object is created with a null first name

  • The constructor throws an IllegalArgumentException and no object is returned to the caller

  • The constructor silently replaces the null with an empty string

Reveal answer
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.

java
Notes on the code
  • The compact canonical constructor has no parameter list; the record's components are supplied implicitly.
  • The only statement in the body is the firstName check.
  • 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.

Key point

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.

newFullName(onlyName)One-argumentconstructorthis(onlyName, null)Canonical constructorfirstName is null?ThrowIllegalArgumentExceptionObject acceptedcallsdelegatesinvokesruns guardyesno
When an overloaded constructor calls this(...), which constructor's body runs, and how does control pass from one constructor to the other?

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.

CallerOne-argumentconstructorCanonical constructornew FullName(onlyName)this(onlyName, null)guard passes or throwsobject returned or exception propagates
When new is called on an overloaded constructor, in what order do the constructor bodies execute, and which one finishes first?

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.

Worked example

Tracing a creation that fails the guard

Trace what happens to firstName, lastName, and the caller's variable when new FullName(null, "Lovelace") runs.

  1. 1
    Call the canonical constructor

    Two arguments are supplied, so the canonical constructor runs directly. firstName is null and lastName is Lovelace.

  2. 2
    Evaluate the guard

    The check firstName == null is true, so the if-branch is taken.

  3. 3
    Throw the exception

    An IllegalArgumentException with the message First name can't be null. is thrown. The constructor does not finish normally.

  4. 4
    Inspect 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.

Result

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 like

    Looking only at public FullName(String onlyName) and concluding that a null value is handled there.

    Why it fails

    The one-argument constructor only calls this(onlyName, null). The guard lives in the canonical constructor.

    Do this instead

    Follow 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 like

    Expecting a FullName variable to exist after new FullName(null, "Lovelace") throws.

    Why it fails

    The constructor does not finish normally, so no usable object is returned to the caller.

    Do this instead

    Expect 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 fails

    this(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 fails

    In 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.

  1. 1
    Identify which constructor the new expression actually matched, including any overloaded constructor that was called.
  2. 2
    Follow each this(...) call to the constructor it invokes, noting the exact arguments passed.
  3. 3
    Find the guard in the canonical constructor body that decides whether the object is accepted.
  4. 4
    Check the values that reach that guard, since the outcome depends on them rather than on the caller's intent.

Practice Tracing a Constructor Chain

MEDIUM

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 firstName holds 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 firstName null 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.