Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Exceptions provide separation of error handling code from the normal flow of the application. It's not uncommon to throw an exception during the instantiation of an object.

In this article, we'll examine all the details about throwing exceptions in constructors.

2. Throwing Exceptions in Constructors

Constructors are special types of methods invoked to create an object. In the following sections, we'll look into how to throw exceptions, which exceptions to throw, and why we would throw exceptions in constructors.

2.1. How?

Throwing exceptions in the constructor is no different from doing so in any other method. Let's start by creating an Animal class with a no-arg constructor:

public Animal() throws InstantiationException {
    throw new InstantiationException("Cannot be instantiated");
}

Here, we're throwing InstantiationException, which is a checked exception.

2.2. Which Ones?

Even though throwing any type of exception is allowed, let's establish some best practices.

First, we don't want to throw “java.lang.Exception”. This is because the caller cannot possibly identify what kind of exception and thereby handle it.

Second, we should throw a checked exception if the caller has to forcibly handle it.

Third, we should throw an unchecked exception if a caller cannot recover from the exception.

It's important to note that these practices are equally applicable for both methods and constructors.

2.3. Why?

In this section, let's understand why we might want to throw exceptions in the constructor.

Argument validation is a common use case for throwing exceptions in the constructor. Constructors are mostly used to assign values of variables. If the arguments passed to the constructor are invalid, we can throw exceptions. Let's consider a quick example:

public Animal(String id, int age) {
    if (id == null)
        throw new NullPointerException("Id cannot be null");
    if (age < 0)
        throw new IllegalArgumentException("Age cannot be negative");
}

In the above example, we're performing argument validation before initializing the object. This helps to ensure that we're creating only valid objects.

Here, if the id passed to the Animal object is null, we can throw NullPointerException For arguments that are non-null but still invalid, such as a negative value for age, we can throw an IllegalArgumentException.

Security checks are another common use case for throwing exceptions in the constructor. Some of the objects need security checks during their creation. We can throw exceptions if the constructor performs a possibly unsafe or sensitive operation.

Let's consider our Animal class is loading attributes from a user input file:

public Animal(File file) throws SecurityException, IOException {
    if (file.isAbsolute()) {
        throw new SecurityException("Traversal attempt");
    }
    if (!file.getCanonicalPath()
        .equals(file.getAbsolutePath())) {
        throw new SecurityException("Traversal attempt");
    }
}

In our example above, we prevented the Path Traversal attack. This is achieved by not allowing absolute paths and directory traversal. For example, consider file “a/../b.txt”. Here, the canonical path and the absolute path are different, which can be a potential Directory Traversal attack.

3. Inherited Exceptions in Constructors

Now, let's talk about handling superclass exceptions in constructors.

Let's create a child class, Bird, that extends our Animal class:

public class Bird extends Animal {
    public Bird() throws ReflectiveOperationException {
        super();
    }
    public Bird(String id, int age) {
        super(id, age);
    }
}

Since super() has to be the first line in the constructor, we can't simply insert a try-catch block to handle the checked exception thrown by the superclass.

Since our parent class Animal throws the checked exception InstantiationException, we can't handle the exception in the Bird constructor. Instead, we can propagate the same exception or its parent exception.

It's important to note that the rule for exception handling with respect to method overriding is different. In method overriding, if the superclass method declares an exception, the subclass overridden method can declare the same, subclass exception, or no exception, but cannot declare a parent exception.

On the other hand, unchecked exceptions need not be declared, nor can they be handled inside subclass constructors.

4. Security Concerns

Throwing an exception in a constructor can lead to partially initialized objects. As described in Guideline 7.3 of Java Secure Coding Guidelines, partially initialized objects of a non-final class are prone to a security concern known as a Finalizer Attack.

In short, a Finalizer attack is induced by subclassing partially initialized objects and overriding its finalize() method, and attempts to create a new instance of that subclass. This will possibly bypass the security checks done inside the constructor of the subclass.

Overriding the finalize() method and marking it final can prevent this attack.

However, the finalize() method has been deprecated in Java 9, thus preventing this type of attack.

5. Conclusion

In this tutorial, we've learned about throwing exceptions in constructors, along with the associated benefits and security concerns. Also, we took a look at some best practices for throwing exceptions in constructors.

As always, the source code used in this tutorial is available over on GitHub.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Junit footer banner
Comments are closed on this article!