Making our way through our detailed Java Exception Handling series we arrive at the AssertionError that we'll be going over today. As with many other languages, the AssertionError
in Java is thrown when an assert
statement fails (i.e. the result is false).
Within today's article we'll explore the detailed of the AssertionError
by first looking at where it sits in the larger Java Exception Hierarchy. We'll also examine some fully functional Java code that will illustrate one example of a best practice for using assertions in Java applications, and how such code should lead to an unexpected AssertionError
if something goes wrong. Let's take a look!
All Java errors implement the java.lang.Throwable
interface, or are extended from another inherited class therein. The full exception hierarchy of this error is:
java.lang.Object
java.lang.Throwable
java.lang.Error
java.lang.AssertionError
Below is the full code sample we'll be using in this article. It can be copied and pasted if you'd like to play with the code yourself and see how everything works.
package io.airbrake;
import io.airbrake.utility.Logging;
import java.util.GregorianCalendar;
public class Main {
public static void main(String[] args) {
testStringPublicationType();testEnumPublicationType();
}private static void testEnumPublicationType() {
try {
// Create book with PublicationType: PublicationType.PAPERBACK
Book book = new Book(
"The Name of the Wind",
"Patrick Rothfuss",
662,
new GregorianCalendar(2007, 2, 27).getTime(),
PublicationType.PAPERBACK);
// Output Book.
Logging.log(book);
// Change to invalid publication type.
Logging.lineSeparator("CHANGE PUBLICATION TYPE TO 'PublicationType.INVALID'", 60);
//book.setPublicationType(PublicationType.INVALID);
// Output modified Book.
Logging.log(book);
} catch (AssertionError error) {
// Output expected AssertionErrors.
Logging.log(error);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}
private static void testStringPublicationType() {
try {
// Create book with PublicationType: 'PAPERBACK'
BookWithStringPublicationType bookWithStringPublicationType = new BookWithStringPublicationType(
"The Wise Man's Fear",
"Patrick Rothfuss",
994,
new GregorianCalendar(2011, 2, 1).getTime(),
"PAPERBACK");
// Output Book.
Logging.log(bookWithStringPublicationType);
// Change to invalid publication type.
Logging.lineSeparator("CHANGE PUBLICATION TYPE TO 'INVALID' String", 60);
bookWithStringPublicationType.setPublicationType("INVALID");
// Output modified Book.
Logging.log(bookWithStringPublicationType);
} catch (AssertionError error) {
// Output expected AssertionErrors.
Logging.log(error);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}
}
// PublicationType.java
package io.airbrake;
public enum PublicationType {
DIGITAL,
PAPERBACK,
}
// Book.java
package io.airbrake;import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.airbrake.utility.Logging;import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.util.Date;/**
* Simple example class to store book instances.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Book
{
private String author;
private String title;
private Integer pageCount;
private Date publishedAt;
private PublicationType publicationType = PublicationType.DIGITAL;private static final Integer maximumPageCount = 4000;
/**
* Constructs an empty book.
*/
public Book() { }/**
* Constructs a basic book.
*
* @param title Book title.
* @param author Book author.
*/
public Book(String title, String author) {
setAuthor(author);
setTitle(title);
}/**
* Constructs a basic book, with page count.
*
* @param title Book title.
* @param author Book author.
* @param pageCount Book page count.
*/
public Book(String title, String author, Integer pageCount) {
setAuthor(author);
setPageCount(pageCount);
setTitle(title);
}/**
* Constructs a basic book, with page count.
*
* @param title Book title.
* @param author Book author.
* @param pageCount Book page count.
*/
public Book(String title, String author, Integer pageCount, Date publishedAt) {
setAuthor(author);
setPageCount(pageCount);
setTitle(title);
setPublishedAt(publishedAt);
}/**
* Constructs a basic book, with page count.
*
* @param title Book title.
* @param author Book author.
* @param pageCount Book page count.
*/
public Book(String title, String author, Integer pageCount, Date publishedAt, PublicationType publicationType) {
setAuthor(author);
setPageCount(pageCount);
setTitle(title);
setPublishedAt(publishedAt);
setPublicationType(publicationType);
}/**
* Get author of book.
*
* @return Author name.
*/
public String getAuthor() {
return author;
}/**
* Get page count of book.
*
* @return Page count.
*/
public Integer getPageCount() {
return pageCount;
}/**
* Get publication type of book.
*
* @return Publication type.
*/
public PublicationType getPublicationType() { return publicationType; }/**
* Get published date of book.
*
* @return Published date.
*/
public Date getPublishedAt() { return publishedAt; }/**
* Get a formatted tagline with author, title, page count, publication date, and publication type.
*
* @return Formatted tagline.
*/
public String getTagline() {
return String.format("'%s' by %s is %d pages, published %s as %s type.",
getTitle(),
getAuthor(),
getPageCount(),
DateFormat.getDateInstance().format(getPublishedAt()),
getPublicationType());
}/**
* Get title of book.
*
* @return Title.
*/
public String getTitle() {
return title;
}/**
* Publish current book.
* If book already published, throws IllegalStateException.
*/
public void publish() throws IllegalStateException {
Date publishedAt = getPublishedAt();
if (publishedAt == null) {
setPublishedAt(new Date());
Logging.log(String.format("Published '%s' by %s.", getTitle(), getAuthor()));
} else {
throw new IllegalStateException(
String.format("Cannot publish '%s' by %s (already published on %s).",
getTitle(),
getAuthor(),
publishedAt));
}
}/**
* Set author of book.
*
* @param author Author name.
*/
public void setAuthor(String author) {
this.author = author;
}/**
* Set page count of book.
*
* @param pageCount Page count.
*/
public void setPageCount(Integer pageCount) throws IllegalArgumentException {
if (pageCount > maximumPageCount) {
throw new IllegalArgumentException(String.format("Page count value [%d] exceeds maximum limit [%d].", pageCount, maximumPageCount));
}
this.pageCount = pageCount;
}/**
* Set publication type of book.
*
* @param type Publication type.
*/
public void setPublicationType(PublicationType type) { this.publicationType = type; }/**
* Set published date of book.
*
* @param publishedAt Page count.
*/
public void setPublishedAt(Date publishedAt) {
this.publishedAt = publishedAt;
}/**
* Set title of book.
*
* @param title Title.
*/
public void setTitle(String title) {
this.title = title;
}/**
* Get the filename formatted version of this Book.
*
* @return Filename.
*/
String toFilename() {
try {
return java.net.URLEncoder.encode(String.format("%s-%s", getTitle(), getAuthor()).toLowerCase(), "UTF-8");
} catch (UnsupportedEncodingException exception) {
Logging.log(exception);
}
return null;
}/**
* Output to JSON string.
*
* @return
* @throws JsonProcessingException
*/
public String toJsonString() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(this);
}/**
* Get string representation of Book.
*
* @return String representation.
*/
public String toString() {
return getTagline();
}
/**
* Throw an Exception.
*/
public void throwException(String message) throws Exception {
throw new Exception(message);
}
}
This code sample also uses the Logging
utility class, the source of which can be found here on GitHub.
To understand how AssertionErrors
should be thrown within Java code we should first briefly review how assertions and the assert
keyword are intended for use. The purpose of assertion is to test assumptions about the application's logic. These assumptions might include pre-conditions, post-conditions, invariants, and the like. As with most other programming assertion features, the Java assert
keyword expects a boolean expression that the code assumes will be/should be true. This is because, the moment an assert
expression is false the Java Virtual Machine (JVM
) will throw an AssertionError
, which should typically halt execution of the application.
Before we go any further it's worth noting that, by default, assertions are disabled for most JVMs. Thus, executing code with a failing assertion will completely ignore the failure and will not throw an AssertionError
. This is typically not desired, since the purpose of using assert
expressions at all is to properly test the assumptions throughout your code. To enable assertion checking you'll need to add a command-line switch: -enableassertions
or -ea
. For example, if you're executing code via the java
command line tool, you'd need to issue something like the following command: $ java -ea <class>
. For most IDEs such as IntelliJ IDEA or Eclipse, you should look in the run configuration
for JVM/VM
command-line options, in which you can add the -ea
flag.
There is a bit of discussion and debate in the Java development community about the exact and proper use of assertions, but the general consensus is that the appearance of an AssertionError
should indicate a fundamentally broken application/code snippet. Consequently, assert
statements should be used as a form of sanity checks as final "no-turning-back" statements that should never be reachable by proper code. In other words, it is common to use an assert
statement that always produces a false
value, yet in a location where execution of the assert
statement should not be possible.
To illustrate this particular usage of assertions we've created a modified version of our Book
class called BookWithStringPublicationType
:
// Book.java
package io.airbrake;import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.airbrake.utility.Logging;import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.util.Date;/**
* Simple example class to store book instances.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class BookWithStringPublicationType
{
private String author;
private String title;
private Integer pageCount;
private Date publishedAt;
private String publicationType = "DIGITAL";private static final Integer maximumPageCount = 4000;
/**
* Constructs an empty book.
*/
public BookWithStringPublicationType() { }/**
* Constructs a basic book.
*
* @param title Book title.
* @param author Book author.
*/
public BookWithStringPublicationType(String title, String author) {
setAuthor(author);
setTitle(title);
}/**
* Constructs a basic book, with page count.
*
* @param title Book title.
* @param author Book author.
* @param pageCount Book page count.
*/
public BookWithStringPublicationType(String title, String author, Integer pageCount) {
setAuthor(author);
setPageCount(pageCount);
setTitle(title);
}/**
* Constructs a basic book, with page count.
*
* @param title Book title.
* @param author Book author.
* @param pageCount Book page count.
*/
public BookWithStringPublicationType(String title, String author, Integer pageCount, Date publishedAt) {
setAuthor(author);
setPageCount(pageCount);
setTitle(title);
setPublishedAt(publishedAt);
}/**
* Constructs a basic book, with page count.
*
* @param title Book title.
* @param author Book author.
* @param pageCount Book page count.
*/
public BookWithStringPublicationType(String title, String author, Integer pageCount, Date publishedAt, String publicationType) {
setAuthor(author);
setPageCount(pageCount);
setTitle(title);
setPublishedAt(publishedAt);
setPublicationType(publicationType);
}/**
* Get author of book.
*
* @return Author name.
*/
public String getAuthor() {
return author;
}/**
* Get page count of book.
*
* @return Page count.
*/
public Integer getPageCount() {
return pageCount;
}/**
* Get publication type of book.
*
* @return Publication type.
*/
public String getPublicationType() { return publicationType; }/**
* Get published date of book.
*
* @return Published date.
*/
public Date getPublishedAt() { return publishedAt; }/**
* Get a formatted tagline with author, title, page count, publication date, and publication type.
*
* @return Formatted tagline.
*/
public String getTagline() {
return String.format("'%s' by %s is %d pages, published %s as %s type.",
getTitle(),
getAuthor(),
getPageCount(),
DateFormat.getDateInstance().format(getPublishedAt()),
getPublicationType());
}/**
* Get title of book.
*
* @return Title.
*/
public String getTitle() {
return title;
}/**
* Publish current book.
* If book already published, throws IllegalStateException.
*/
public void publish() throws IllegalStateException {
Date publishedAt = getPublishedAt();
if (publishedAt == null) {
setPublishedAt(new Date());
Logging.log(String.format("Published '%s' by %s.", getTitle(), getAuthor()));
} else {
throw new IllegalStateException(
String.format("Cannot publish '%s' by %s (already published on %s).",
getTitle(),
getAuthor(),
publishedAt));
}
}/**
* Set author of book.
*
* @param author Author name.
*/
public void setAuthor(String author) {
this.author = author;
}/**
* Set page count of book.
*
* @param pageCount Page count.
*/
public void setPageCount(Integer pageCount) throws IllegalArgumentException {
if (pageCount > maximumPageCount) {
throw new IllegalArgumentException(String.format("Page count value [%d] exceeds maximum limit [%d].", pageCount, maximumPageCount));
}
this.pageCount = pageCount;
}/**
* Set publication type of book.
*
* @param type Publication type.
*/
void setPublicationType(String type) {
switch(type) {
case "DIGITAL":
this.publicationType = type;
break;
case "PAPERBACK":
this.publicationType = type;
break;
// Default assertion should never execute; otherwise, code is improper.
default: assert false : String.format("PublicationType of [%s] is unacceptable.", type);
}
}/**
* Set published date of book.
*
* @param publishedAt Page count.
*/
public void setPublishedAt(Date publishedAt) {
this.publishedAt = publishedAt;
}/**
* Set title of book.
*
* @param title Title.
*/
public void setTitle(String title) {
this.title = title;
}/**
* Get the filename formatted version of this Book.
*
* @return Filename.
*/
String toFilename() {
try {
return java.net.URLEncoder.encode(String.format("%s-%s", getTitle(), getAuthor()).toLowerCase(), "UTF-8");
} catch (UnsupportedEncodingException exception) {
Logging.log(exception);
}
return null;
}/**
* Output to JSON string.
*
* @return
* @throws JsonProcessingException
*/
public String toJsonString() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(this);
}/**
* Get string representation of Book.
*
* @return String representation.
*/
public String toString() {
return getTagline();
}
/**
* Throw an Exception.
*/
public void throwException(String message) throws Exception {
throw new Exception(message);
}
}
As the name indicates we're using a simple String
value to store the PublicationType
field of each Book
. We then perform a simple sanity check prior to setting the value within the setPublicationType(String type)
method:
/**
* Set publication type of book.
*
* @param type Publication type.
*/
void setPublicationType(String type) {
switch(type) {
case "DIGITAL":
this.publicationType = type;
break;
case "PAPERBACK":
this.publicationType = type;
break;
// Default assertion should never execute; otherwise, code is improper.
default:
assert false : String.format("PublicationType of [%s] is unacceptable.", type);
}
}
As you can see we perform a simple switch
case test on the passed String type
parameter. If it is one of the two valid values we set the value and explicitly break
from the switch
statement. However, any other value will reach the default
case, which contains an always-false assert false
expression, with an additional argument passed to it that will be used for the error message of the subsequent AssertionError
instance. In this case, the goal of the code is to be completely certain that a publicationType
field can never be set to something that is invalid. Attempting to do so will produce a failure via an AssertionError
, which will require alteration by a developer to fix the bug.
To illustrate how this works in practice we have the testEnumPublicationType
method:
private static void testStringPublicationType() {
try {
// Create book with PublicationType: 'PAPERBACK'
BookWithStringPublicationType bookWithStringPublicationType = new BookWithStringPublicationType(
"The Wise Man's Fear",
"Patrick Rothfuss",
994,
new GregorianCalendar(2011, 2, 1).getTime(),
"PAPERBACK");
// Output Book.
Logging.log(bookWithStringPublicationType);
// Change to invalid publication type.
Logging.lineSeparator("CHANGE PUBLICATION TYPE TO 'INVALID' String", 60);
bookWithStringPublicationType.setPublicationType("INVALID");
// Output modified Book.
Logging.log(bookWithStringPublicationType);
} catch (AssertionError error) {
// Output expected AssertionErrors.
Logging.log(error);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}
We start by creating a valid BookWithStringPublicationType
instance with a publicationType
property of "PAPERBACK"
. We then try to change to publicationType
to "INVALID"
before outputting the resulting object. To give us something to show when a failure occurs we're explicitly catching any AssertionErrors
that are thrown here, but in normal code we would not want to catch
such errors, allowing them to instead crash the application.
Executing the testStringPublicationType()
method produces the following output:
io.airbrake.BookWithStringPublicationType@50675690[
author=Patrick Rothfuss
title=The Wise Man's Fear
pageCount=994
publishedAt=Tue Mar 01 00:00:00 PST 2011
publicationType=PAPERBACK
]
------- CHANGE PUBLICATION TYPE TO 'INVALID' String --------
[EXPECTED] java.lang.AssertionError: PublicationType of [INVALID] is unacceptable.
Everything works just as expected. Our original BookWithStringPublicationType
instance as a "PAPERBACK"
is instantiating just fine, but attempting to change it to "INVALID"
throws an AssertionError
, since execution within the BookWithStringPublicationType.setPublicationType(String type)
method reached the final, default
switch case and executing the assert false
statement.
While this example illustrates one common way to use assertions in Java, we can also modify how the BookWithStringPublicationType
class handles the publicationType
to ensure it doesn't rely on an assertion statement, while also ensuring that code cannot try to set invalid values for the field. We do this for the plain Book
class by using the PublicationType
enum
:
// PublicationType.java
package io.airbrake;
public enum PublicationType {
DIGITAL,
PAPERBACK,
}
Since only the publicationType
field and its related getter/setter methods were modified to use the PublicationType
enum we'll only look at these changes in code:
public class Book
{
// ...private PublicationType publicationType = PublicationType.DIGITAL;
/**
* Get publication type of book.
*
* @return Publication type.
*/
public PublicationType getPublicationType() { return publicationType; }/**
* Set publication type of book.
*
* @param type Publication type.
*/
public void setPublicationType(PublicationType type) { this.publicationType = type; }
// ...
}
As with enumerations in other languages, using one in Java allows us to maintain a collection of valid values for a particular data type. To illustrate this the testEnumPublicationType()
method seen below creates an intial Book
instance with the PublicationType.PAPERBACK
value for publicationType
, then attempts to invoke the book.setPublicationType(PublicationType.INVALID)
method call to change it:
private static void testEnumPublicationType() {
try {
// Create book with PublicationType: PublicationType.PAPERBACK
Book book = new Book(
"The Name of the Wind",
"Patrick Rothfuss",
662,
new GregorianCalendar(2007, 2, 27).getTime(),
PublicationType.PAPERBACK);
// Output Book.
Logging.log(book);
// Change to invalid publication type.
Logging.lineSeparator("CHANGE PUBLICATION TYPE TO 'PublicationType.INVALID'", 60);
book.setPublicationType(PublicationType.INVALID);
// Output modified Book.
Logging.log(book);
} catch (AssertionError error) {
// Output expected AssertionErrors.
Logging.log(error);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}
As it happens, since the JVM can identify the enumeration that is used here during compilation time, we can't even execute this code -- the compiler detects that PublicationType.INVALID
is, well, not a valid symbol within the PublicationType
enum, so it halts compilation and delivers an error message. This implementation serves the same purpose of the assert
technique seen above, except it captures an issue at compilation/development time, rather than during runtime.
The Airbrake-Java library provides real-time error monitoring and automatic exception reporting for all your Java-based projects. Tight integration with Airbrake's state of the art web dashboard ensures that Airbrake-Java
gives you round-the-clock status updates on your application's health and error rates. Airbrake-Java
easily integrates with all the latest Java frameworks and platforms like Spring
, Maven
, log4j
, Struts
, Kotlin
, Grails
, Groovy
, and many more. Plus, Airbrake-Java
allows you to easily customize exception parameters and gives you full, configurable filter capabilities so you only gather the errors that matter most.
Check out all the amazing features Airbrake-Java has to offer and see for yourself why so many of the world's best engineering teams are using Airbrake to revolutionize their exception handling practices! Try Airbrake free for 30 days.