Programming with Assertions in Java Part 1
Sep 24, 2018 • 9 Minute Read
Introduction
You have probably used assertion methods when unit testing through frameworks like JUnit or third-party assertion libraries like AssertJ.
But did you know that Java has a built-in assert mechanism? If so, have you used it before?
Java's assertions were introduced way back in Java 1.4 and, like the assertion methods of unit testing frameworks, they use a boolean expression to throw an exception if it evaluates to false.
However, they can be used anywhere in the code (with a few restrictions due to some good practices) and with different semantics than the other assertion methods.
Assertions test things that you assume are true.
It may be things that you consider impossible to happen, things that must be present after executing a piece of code, or something that must be true for your code to work.
This may sound like a simple concept, but it's the key to understanding and using assertions properly.
In this series of guides, I'll show you how to use the Java assertion mechanism, the syntax and options to enable and disable assertions, good assertion practices, the Design by Contract style of programming using assertions, and how assertions differentiate to exceptions and unit tests.
Using Assertions
Let's start with the basics of how to use assertions.
Assertions use the reserved assert keyword. It has the following syntax:
assert booleanExpression;
Here's an example:
private double calculateInterest(double amount) {
assert amount > 0 && amount <= 1_000;
double interest = 0.0;
// calculate interest
return interest;
}
If the condition evaluates to false, an exception of type java.lang.AssertionError will be thrown.
This means that the above example is equivalent to the following:
private double calculateInterest(double amount) {
if(amount > 0 && amount < 1_000) {
throw new AssertionError();
}
double interest = 0.0;
// calculate interest
return interest;
}
Why is AssertionError a subclass of Error rather than RuntimeException?
Because we are not supposed to catch Error and its subclasses.
It doesn't make sense to try to recover from assertion errors. Such an error would mean that the program is not operating under conditions assumed by the programmer to be true. This would make it very likely that continuing the execution will lead to more errors down the line.
So the error will cause the program to stop and a stack trace will be printed, for example:
Exception in thread "main" java.lang.AssertionError
at com.example.AssertTest.calculateInterest(AssertTest.java:6)
at com.example.AssertTest.main(AssertTest.java:16)
That may not be very useful.
However, an assert statement can also take a message:
assert booleanExpression : "Message about the error";
For example, if the following assertion fails:
private double calculateInterest(double amount) {
assert
amount > 0 && amount < 1_000
:
"Amount should be between 0 and 1,000: "
+ amount;
// ...
}
Something like the following will be printed to the standard output:
Exception in thread "main" java.lang.AssertionError: Amount should be between 0 and 1,000: -11.0
at com.example.AssertTest.calculateInterest(AssertTest.java:6)
at com.example.AssertTest.main(AssertTest.java:16)
Once again, the above example is equivalent to:
if(amount > 0 && amount < 1_000) {
throw new AssertionError(
"Amount should be between 0 and 1,000: "
+ amount
);
}
// ...
}
Most of the time, you'll use a String but, if you look at the constructors of AssertionError, you'll see that it can also take other types that will be converted to a string:
AssertionError(boolean detailMessage)
AssertionError(char detailMessage)
AssertionError(double detailMessage)
AssertionError(float detailMessage)
AssertionError(int detailMessage)
AssertionError(long detailMessage)
AssertionError(Object detailMessage)
AssertionError(String message, Throwable cause)
Which means that if that is enough for you, you can use something like:
private double calculateInterest(double amount) {
assert amount > 0 && amount < 1_000 : amount;
// ...
}
If the boolean expression evaluates to true, nothing will happen.
However, assertions are not enabled by default. So, even if the assertion fails, if you don't run your program with a special flag, nothing will happen.
Assertions are enabled with either the -ea or enableassertions flags:
java –ea AssertTest
// Or
java –enableassertions AssertTest
This would enable assertions in all the classes of our program except for Java classes (system classes).
If you want to enable assertions in Java classes, you can use the -esa or enablesystemassertions flags:
java –esa AssertTest
// Or
java –enablesystemassertions AssertTest
You can also enable assertions for just one class:
java –ea:com.example.MyOtherClass AssertTest
Or in a specific named package and any of its subpackages:
java –ea:com.example... AssertTest
Or in the default package in the current working directory:
java –ea:... AssertTest
There's also an option to disable assertions:
java –da AssertTest
// Or
java –disableassertions AssertTest
And the corresponding option to disable assertions in Java classes:
java –dsa AssertTest
// Or
java –disablesystemassertions AssertTest
You might be wondering, why do we have this option if assertions are disabled by default?
Well, you can use the same options of the ea and esa flags to disable just one class or entire packages.
And you can also combine them. For example, you can enable assertions in all the classes of the package com.example except for the class com.example.Utils:
java –ea:com.example... -da:com.example.Utils AssertTest
Design-by-Contract Style and Assertions
Java's assert mechanism can be used for an informal design-by-contract style of programming.
Design-by-Contract is an approach to designing software that views software as a set of components whose interactions are based on mutual defined obligations or contracts in the form of:
- Acceptable and unacceptable input and return values or types
- Error and exception condition that can occur
- Side effects
- Preconditions
- Postconditions
- Invariants
This concept was conceived by Bertrand Meyer, who designed the Eiffel programming language based on this and other concepts of object-oriented programming.
For example, in Eiffel, you can have a routine with the following syntax:
processElement (e : ELEMENT) is
require
not e.empty
do
-- Perform the operation
ensure
e.is_processed
end
Similar to assert, the require clause checks the input, or preconditions, while the ensure clause checks the output or post-conditions. Both of these conditions are part of the contract associated with this routine.
But we can also have conditions that must apply to the whole class all the time (like before and after calling one of its methods), not just at a certain moment. These are known as class invariants:
invariant
element_count > 0
You can think of invariants as a condition that is a precondition and a post-condition at the same time.
This way, Eiffel's IDE can provide a short form of the class in text format, without all the implementation of the class and just retaining the contract information:
class MyClass
feature
processElement (e : ELEMENT) is
require
not e.empty
ensure
e.is_processed
end
-- Other features or routines
invariant
element_count > 0
end
We don't have clauses or documentation mechanism like these in Java, but the preconditions, post-conditions, and invariants are really some forms of assertions that can be implemented with the assert keyword.
You have seen examples of preconditions and post-conditions (just remember that it's not recommended to use assertions to check the input of public methods), but in the case of invariants, Java assertions don't particularly enforce class or any kind of invariants.
If you want to do something equivalent that Eiffel class invariants, the recommended approach is to create a private method that checks for the invariant conditions:
private boolean checkClassInvariants() {
// ...
}
And use this method in assert statements placed at the beginning or/and at end of public methods, constructors, or any other places you think it will make sense:
assert checkClassInvariants();
Next Steps
In the next guide, Programming with Assertions in Java Part 2, I'll discuss some best practices when using assertions.