Our journey continues through our in-depth Java Exception Handling series as, today, we dig into the depths of the NoSuchElementException. As the name suggests, a NoSuchElementException
is thrown when trying to access an invalid element using a few built-in methods from the Enumeration
and Iterator
classes.
Throughout this article we'll examine the NoSuchElementException
in greater detail by looking at where it sits in the overall Java Exception Hierarchy. We'll also get into some function Java code samples that will illustrate the basic usage of an Iterator
, how it compares to more modern Java versions collection iteration practices, and how improper use of Iterators
can lead to NoSuchElementExceptions
in your own code, so let's get going!
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.Exception
java.lang.RuntimeException
java.util.NoSuchElementException
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.ArrayList;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.NoSuchElementException;public class Main {
public static void main(String[] args) {
try {
Logging.lineSeparator("CREATING BOOKS");
ArrayList<Book> books = new ArrayList<>();
books.add(
new Book(
"The Stand",
"Stephen King",
1153,
new GregorianCalendar(1978, 8, 1).getTime()
)
);books.add(
new Book(
"It",
"Stephen King",
1116,
new GregorianCalendar(1987, 9, 1).getTime()
)
);books.add(
new Book(
"The Gunslinger",
"Stephen King",
231,
new GregorianCalendar(1982, 5, 10).getTime()
)
);Logging.lineSeparator("FOREACH LOOP TEST");
forEachLoopTest(books);Logging.lineSeparator("ITERATOR TEST");
Iterator iterator = iteratorTest(books);Logging.log(iterator.next());
} catch (NoSuchElementException exception) {
// Output expected NoSuchElementExceptions.
Logging.log(exception);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}/**
* Loops through passed ArrayList using built-in forEach() method. Outputs elements to log.
*
* @param list List to be looped through.
*/
private static void forEachLoopTest(ArrayList<Book> list) {
try {
// Output list via forEach method and Logging::log method reference.
list.forEach(Logging::log);
} catch (NoSuchElementException exception) {
// Output expected NoSuchElementExceptions.
Logging.log(exception);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}
/**
* Loops through passed ArrayList using Iterator of list and converting each element to Book before output.
*
* @param list List to be iterated through.
* @return Iterator obtained from ArrayList.
*/
private static Iterator iteratorTest(ArrayList<Book> list) {
try {
// Create iterator from list.
Iterator iterator = list.iterator();
// While next element exists, iteratorTest.
while (iterator.hasNext())
{
// Get next element and output.
Book book = (Book) iterator.next();
Logging.log(book);
}
// Return iterator.
return iterator;
} catch (NoSuchElementException exception) {
// Output expected NoSuchElementExceptions.
Logging.log(exception);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
return null;
}
}
// 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, publication date, and publication type.
*
* @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);
}
}
// Logging.java
package io.airbrake.utility;import java.sql.Timestamp;
import java.util.Arrays;import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.builder.*;/**
* Houses all logging methods for various debug outputs.
*/
public class Logging {
private static final char separatorCharacterDefault = '-';
private static final String separatorInsertDefault = "";
private static final int separatorLengthDefault = 40;/**
* Get a String of passed char of passed length size.
* @param character Character to repeat.
* @param length Length of string.
* @return Created string.
*/
private static String getRepeatedCharString(char character, int length) {
// Create new character array of proper length.
char[] characters = new char[length];
// Fill each array element with character.
Arrays.fill(characters, character);
// Return generated string.
return new String(characters);
}/**
* Outputs any kind of Object.
* Uses ReflectionToStringBuilder from Apache commons-lang library.
*
* @param value Object to be output.
*/
public static void log(Object value)
{
if (value == null) return;
// If primitive or wrapper object, directly output.
if (ClassUtils.isPrimitiveOrWrapper(value.getClass()))
{
System.out.println(value);
}
else
{
// For complex objects, use reflection builder output.
System.out.println(new ReflectionToStringBuilder(value, ToStringStyle.MULTI_LINE_STYLE).toString());
}
}/**
* Outputs any kind of String.
*
* @param value String to be output.
*/
public static void log(String value)
{
if (value == null) return;
System.out.println(value);
}/**
* Outputs any kind of String, with prefixed timestamp.
*
* @param value String to be output.
* @param includeTimestamp Indicates if timestamp should be included.
*/
public static void log(String value, boolean includeTimestamp)
{
if (value == null) return;
if (includeTimestamp) {
System.out.println(String.format("[%s] %s", new Timestamp(System.currentTimeMillis()), value));
} else {
log(value);
}
}/**
* Outputs passed in Throwable exception or error instance.
* Can be overloaded if expected parameter should be specified.
*
* @param throwable Throwable instance to output.
*/
public static void log(Throwable throwable)
{
// Invoke call with default expected value.
log(throwable, true);
}/**
* Outputs passed in Throwable exception or error instance.
* Includes Throwable class type, message, stack trace, and expectation status.
*
* @param throwable Throwable instance to output.
* @param expected Determines if this Throwable was expected or not.
*/
public static void log(Throwable throwable, boolean expected)
{
System.out.println(String.format("[%s] %s", expected ? "EXPECTED" : "UNEXPECTED", throwable.toString()));
throwable.printStackTrace();
}/**
* See: lineSeparator(String, int, char)
*/
public static void lineSeparator() {
lineSeparator(separatorInsertDefault, separatorLengthDefault, separatorCharacterDefault);
}/**
* See: lineSeparator(String, int, char)
*/
public static void lineSeparator(String insert) {
lineSeparator(insert, separatorLengthDefault, separatorCharacterDefault);
}/**
* See: lineSeparator(String, int, char)
*/
public static void lineSeparator(int length) {
lineSeparator(separatorInsertDefault, length, separatorCharacterDefault);
}/**
* See: lineSeparator(String, int, char)
*/
public static void lineSeparator(int length, char separator) {
lineSeparator(separatorInsertDefault, length, separator);
}/**
* See: lineSeparator(String, int, char)
*/
public static void lineSeparator(char separator) {
lineSeparator(separatorInsertDefault, separatorLengthDefault, separator);
}/**
* See: lineSeparator(String, int, char)
*/
public static void lineSeparator(String insert, int length) {
lineSeparator(insert, length, separatorCharacterDefault);
}/**
* See: lineSeparator(String, int, char)
*/
public static void lineSeparator(String insert, char separator) {
lineSeparator(insert, separatorLengthDefault, separator);
}/**
* Outputs a dashed line separator with
* inserted text centered in the middle.
*
* @param insert Inserted text to be centered.
* @param length Length of line to be output.
* @param separator Separator character.
*/
public static void lineSeparator(String insert, int length, char separator)
{
// Default output to insert.
String output = insert;if (insert.length() == 0) {
output = getRepeatedCharString(separator, length);
} else if (insert.length() < length) {
// Update length based on insert length, less a space for margin.
length -= (insert.length() + 2);
// Halve the length and floor left side.
int left = (int) Math.floor(length / 2);
int right = left;
// If odd number, add dropped remainder to right side.
if (length % 2 != 0) right += 1;// Surround insert with separators.
output = String.format("%s %s %s", getRepeatedCharString(separator, left), insert, getRepeatedCharString(separator, right));
}
System.out.println(output);
}
}
Java includes a few different ways to iterate through elements in a collection. The first of these classes, Enumeration
, was introduced in JDK1.0 and is generally considered deprecated in favor of newer iteration classes, like Iterator
and ListIterator
. As with most programming languages, the Iterator
class includes a hasNext()
method that returns a boolean indicating if the iteration has anymore elements. If hasNext()
returns true
, then the next()
method will return the next element in the iteration. Unlike Enumeration
, Iterator
also has a remove()
method, which removes the last element that was obtained via next()
. While Iterator
is generalized for use with all collections in the Java Collections Framework, ListIterator
is more specialized and only works with List
-based collections, like ArrayList
, LinkedList
, and so forth. However, ListIterator
adds even more functionality by allowing iteration to traverse in both directions via hasPrevious()
and previous()
methods.
For our example today we'll just be sticking with the traditional Iterator
class, along with a comparison to a more modern built-in syntax for iterating over common collections (an ArrayList
, in this case). We start with the forEachLoopTest(ArrayList<Book> list)
:
/**
* Loops through passed ArrayList using built-in forEach() method. Outputs elements to log.
*
* @param list List to be looped through.
*/
private static void forEachLoopTest(ArrayList<Book> list) {
try {
// Output list via forEach method and Logging::log method reference.
list.forEach(Logging::log);
} catch (NoSuchElementException exception) {
// Output expected NoSuchElementExceptions.
Logging.log(exception);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}
This method uses the ArrayList.forEach(Consumer<? super E> action)
method, which provides a simple method for performing an action on every element in an Iterable
collection, such as our ArrayList<Book>
. Moreover, we're using the modern method reference syntax for our lambda expression, which simply passes the Book
element that is obtained in the forEach(...)
loop to our Logging.log
method, which outputs it to the log.
To test this out we create a new ArrayList<Book>
collection and add a few new Books
to it:
public static void main(String[] args) {
try {
Logging.lineSeparator("CREATING BOOKS");
ArrayList<Book> books = new ArrayList<>();
books.add(
new Book(
"The Stand",
"Stephen King",
1153,
new GregorianCalendar(1978, 8, 1).getTime()
)
);books.add(
new Book(
"It",
"Stephen King",
1116,
new GregorianCalendar(1987, 9, 1).getTime()
)
);books.add(
new Book(
"The Gunslinger",
"Stephen King",
231,
new GregorianCalendar(1982, 5, 10).getTime()
)
);Logging.lineSeparator("FOREACH LOOP TEST");
forEachLoopTest(books);Logging.lineSeparator("ITERATOR TEST");
Iterator iterator = iteratorTest(books);
Logging.log(iterator.next());
} catch (NoSuchElementException exception) {
// Output expected NoSuchElementExceptions.
Logging.log(exception);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
}
Executing this code produces the following output:
------------ CREATING BOOKS ------------
---------- FOREACH LOOP TEST -----------
Disconnected from the target VM, address: '127.0.0.1:62285', transport: 'socket'
io.airbrake.Book@5ebec15[
author=Stephen King
title=The Stand
pageCount=1153
publishedAt=Fri Sep 01 00:00:00 PDT 1978
publicationType=DIGITAL
]
io.airbrake.Book@26ba2a48[
author=Stephen King
title=It
pageCount=1116
publishedAt=Thu Oct 01 00:00:00 PDT 1987
publicationType=DIGITAL
]
io.airbrake.Book@5f2050f6[
author=Stephen King
title=The Gunslinger
pageCount=231
publishedAt=Thu Jun 10 00:00:00 PDT 1982
publicationType=DIGITAL
]
As expected, our Books
are created and added to our ArrayList<Book> books
collection, then passed to and iterated through using the forEach(Logging::log)
lambda method expression. This is the simplest way to iterate over all elements, but if we need more explicit control over iteration, we'd likely want to use an actual Iterator
instance, which we're testing in the iteratorTest(ArrayList<Book> list)
method:
/**
* Loops through passed ArrayList using Iterator of list and converting each element to Book before output.
*
* @param list List to be iterated through.
* @return Iterator obtained from ArrayList.
*/
private static Iterator iteratorTest(ArrayList<Book> list) {
try {
// Create iterator from list.
Iterator iterator = list.iterator();
// While next element exists, iteratorTest.
while (iterator.hasNext())
{
// Get next element and output.
Book book = (Book) iterator.next();
Logging.log(book);
}
// Return iterator.
return iterator;
} catch (NoSuchElementException exception) {
// Output expected NoSuchElementExceptions.
Logging.log(exception);
} catch (Exception exception) {
// Output unexpected Exceptions.
Logging.log(exception, false);
}
return null;
}
Here we've obtained the Iterator
instance of our ArrayList<Book> list
collection via the iterator()
method, then perform a loop while iterator.hasNext()
is true
, in which we grab the next element via iterator.next()
, convert it from an Object
to a Book
type, and output it to the log. We also return the obtained Iterator
instance, which we'll use in a moment.
Just as before, this works as expected, so executing this method produces the following output:
------------ ITERATOR TEST -------------
io.airbrake.Book@5ebec15[
author=Stephen King
title=The Stand
pageCount=1153
publishedAt=Fri Sep 01 00:00:00 PDT 1978
publicationType=DIGITAL
]
io.airbrake.Book@26ba2a48[
author=Stephen King
title=It
pageCount=1116
publishedAt=Thu Oct 01 00:00:00 PDT 1987
publicationType=DIGITAL
]
io.airbrake.Book@5f2050f6[
author=Stephen King
title=The Gunslinger
pageCount=231
publishedAt=Thu Jun 10 00:00:00 PDT 1982
publicationType=DIGITAL
]
However, let's see what happens if we use the now-exhausted Iterator
instance returned by the iteratorTest(ArrayList<Book> list)
method and try to obtain the next()
element from it:
Logging.lineSeparator("ITERATOR TEST");
Iterator iterator = iteratorTest(books);
Logging.log(iterator.next());
Unsurprisingly, this throws a NoSuchElementException
our way, because we've already retrieved all three elements of the iterator
within the iteratorTest(ArrayList<Book> list)
method itself:
[EXPECTED] java.util.NoSuchElementException
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.