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.
Let's start with the basics of how to use assertions.
Assertions use the reserved assert
keyword. It has the following syntax:
1assert booleanExpression;
Here's an example:
1private double calculateInterest(double amount) {
2 assert amount > 0 && amount <= 1_000;
3
4 double interest = 0.0;
5
6 // calculate interest
7
8 return interest;
9}
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:
1private double calculateInterest(double amount) {
2 if(amount > 0 && amount < 1_000) {
3 throw new AssertionError();
4 }
5
6 double interest = 0.0;
7
8 // calculate interest
9
10 return interest;
11}
As you might have guessed from its name, AssertionError
is a subclass of Error
:
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:
1Exception in thread "main" java.lang.AssertionError
2 at com.example.AssertTest.calculateInterest(AssertTest.java:6)
3 at com.example.AssertTest.main(AssertTest.java:16)
That may not be very useful.
However, an assert statement can also take a message:
1assert booleanExpression : "Message about the error";
For example, if the following assertion fails:
1private double calculateInterest(double amount) {
2 assert
3 amount > 0 && amount < 1_000
4 :
5 "Amount should be between 0 and 1,000: "
6 + amount;
7
8 // ...
9}
Something like the following will be printed to the standard output:
1Exception in thread "main" java.lang.AssertionError: Amount should be between 0 and 1,000: -11.0
2 at com.example.AssertTest.calculateInterest(AssertTest.java:6)
3 at com.example.AssertTest.main(AssertTest.java:16)
Once again, the above example is equivalent to:
1 if(amount > 0 && amount < 1_000) {
2 throw new AssertionError(
3 "Amount should be between 0 and 1,000: "
4 + amount
5 );
6 }
7
8 // ...
9}
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:
1AssertionError(boolean detailMessage)
2AssertionError(char detailMessage)
3AssertionError(double detailMessage)
4AssertionError(float detailMessage)
5AssertionError(int detailMessage)
6AssertionError(long detailMessage)
7AssertionError(Object detailMessage)
8AssertionError(String message, Throwable cause)
Which means that if that is enough for you, you can use something like:
1private double calculateInterest(double amount) {
2 assert amount > 0 && amount < 1_000 : amount;
3
4 // ...
5}
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:
1java –ea AssertTest
2// Or
3java –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:
1java –esa AssertTest
2// Or
3java –enablesystemassertions AssertTest
You can also enable assertions for just one class:
1java –ea:com.example.MyOtherClass AssertTest
Or in a specific named package and any of its subpackages:
1java –ea:com.example... AssertTest
Or in the default package in the current working directory:
1java –ea:... AssertTest
There's also an option to disable assertions:
1java –da AssertTest
2// Or
3java –disableassertions AssertTest
And the corresponding option to disable assertions in Java classes:
1java –dsa AssertTest
2// Or
3java –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
:
1java –ea:com.example... -da:com.example.Utils AssertTest
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:
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:
1processElement (e : ELEMENT) is
2 require
3 not e.empty
4 do
5 -- Perform the operation
6 ensure
7 e.is_processed
8 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:
1invariant
2 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:
1class MyClass
2feature
3 processElement (e : ELEMENT) is
4 require
5 not e.empty
6 ensure
7 e.is_processed
8 end
9
10 -- Other features or routines
11
12invariant
13 element_count > 0
14end
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:
1private boolean checkClassInvariants() {
2 // ...
3}
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:
1assert checkClassInvariants();
In the next guide, Programming with Assertions in Java Part 2, I'll discuss some best practices when using assertions.