Assertions in Java

One of the rules of defensive programming is to detect the errors as soon as they appear. The main idea behind this is that we can get precise information about the location of the error and the event which caused the error. If we would have ignored the error and therefore let it propagate further through the system, we would receive another error sooner or later. But this time the information about its location and the root cause would be misleading. Moreover, the error may have caused so much damage to the system state and possibly also data so that the only solution would be to restart the system or even restore of the data from backup.

Since Java 1.4 we can use assertions to check for various erroneous situations. Assertion in Java (and also C and C++ to name a few) is a precondition which must be met at a given point of the execution. If the precondition is met, the application continues normally. However, if the precondition is not met, error AssertionError is thrown which immediately aborts the execution of the application. AssertionError may contain additional information describing why the precondition failed.

The typical usage of assert is as follows:

package com.example;

public class Sorter {
    public void sort(int[] array, int left, int right) {
        assert array != null : "Array is not null";
        assert (left >= 0 && left < array.length) : "Invalid left index";
        assert (right >= 0 && right < array.length);
        assert left < right;
    }

}

Keyword assert is followed by a condition which should evaluate to true if everything is fine. The condition may be optionally followed by a colon and the message that should become part of AssertionError and will be presented to user. If the message is omitted, it may be sometimes difficult to precisely identify which assertion failed. Therefore, it is suggested to always provide the message for assertion.

In Java assertions are not checked by default. To force JVM to check them, JVM must be started with -enableassertions or -ea:

[robert@epsilon ~]$ java -ea com.example.Sorter

It is also possible to achieve higher granularity by enabling assertions for selected packages only:

[robert@epsilon asserts]$ java -ea:com.example... com.example.Sorter

or selected classes:

robert@epsilon asserts]$ java -ea:com.example.Sorter com.example.Sorter

There are also options to disable assertions: -disableassertions or -da. The common use case for this would be to have assertions enabled globally but disable them only for specific packages or classes. Though, I personally don’t find this feature useful.

Main uses

Asserts are commonly used to check preconditions, postconditions and invariants inside a method. The preconditions are present at the beginning of the method and usually check arguments passed to the method but they may also check internal state of the object.
Postconditions are present at the very end of the method and ensure that the actions executed in given method did not break internal state of the object and left it in a good shape.
Conditions inside methods are often used to document assumptions made during development.
To make it clear let’s take a look at the example of list which keeps only even values – if value is odd it is rounded up to the nearest even value.

package com.example.blogtest;

import java.util.ArrayList;
import java.util.List;

public class EvenList {
    
    private List<Integer> list = new ArrayList<>();
    
    public void addAllWithRounding(int[] array) {
        // check preconditions
        assert array != null : "Array not null";

        final int oldListSize = list.size();
        for (int value : array) {
            if (value % 2 == 0)
                list.add(value);
            else {
                // document our assumption that the value here would be always odd
                assert value % 2 == 1 : "Value is odd";
                list.add(value + 1);
            }
        }

        // check postconditions
        assert list.size() == oldListSize + array.length : String.format(" %d == %d + %d", list.size(), oldListSize, array.length);
        for (int i = 0; i < list.size(); i++)
            assert list.get(i) % 2 == 0 : String.format("%d is even", list.get(i));
    }
}

Here we have preconditions that check if the passed argument is right. We also have condition that documents and verifies our assumption that at this point of execution the value must be odd. In this case the assumption is trivial but in reality it may not be that obvious. Additionally, at the end of the method we have two postconditions: first checks if the size of the list is correct and the other if the list still contains only even values. Also note that the message for assertion does not have to be a constant but may be dynamically created.

Choosing between assert and throwing exception

If you look at the Sorter or EvenList classes above, you may wonder why not just throw the exception (NullPointerException or IllegalArgumentException) instead of wasting time with assertions (not to mention the need to pass additional option to JVM). The truth is that there is no simple rule defining which to choose and it depends on the concrete situation.

I find assertions to be very useful in postconditions and to document assumptions made inside methods. The usage in preconditions depends strongly on how I expect the method to be called. If the method is private or I control every point from where it can be called and can ensure that the arguments will be always valid, I prefer assert. However, if the method is a part of public interface and can be called by some other people (e.g. is part of library), it is definitely better to throw exceptions.

Assertions in production

The common practice regarding the assertions is that assertions are enabled during development and testing but they are disabled when the final product is installed into production environment. It is sometimes quoted that “removing assertions is a bit like wearing a life-jacket to practice in the harbour, but then leaving the life-jackets behind when your ship leaves for open ocean”. In my opinion this practice somehow conflicts with rules of defensive programming but on the other hand it prevents the situation that the software crashes in front of the customer due to unmet precondition.

Advertisement

About Robert Piasecki

Husband and father, Java software developer, Linux and open-source fan.
This entry was posted in Defensive programming, Java, Software development practices and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.