IllegalArgumentException in Java
Jura Gorohovsky
Author Jura Gorohovsky
Share
IllegalArgumentException in Java

IllegalArgumentException in Java

Jura Gorohovsky
Jura Gorohovsky
11-May-2022
10 min read

Let’s look at IllegalArgumentException, which is one of the most common types of exceptions that Java developers deal with.

We’ll see when and why IllegalArgumentException usually occurs, whether it’s a checked or unchecked exception, as well as how to catch and when to throw it. We’ll use a few examples based on common Java library methods to describe some of the ways to handle IllegalArgumentException.

When and why does IllegalArgumentException usually occur in Java?

IllegalArgumentException is a Java exception indicating that a method has received an argument that is invalid or inappropriate for this method’s purposes.

This exception is normally used when further processing in the method depends on the invalid argument and can not continue unless a proper argument is provided instead.

IllegalArgumentException is commonly used in scenarios where the type of a method’s parameter is not sufficient to properly constrain its possible values. For example, look for an IllegalArgumentException whenever a method expects a string argument that it then internally parses to match a specific pattern.

Is IllegalArgumentException checked or unchecked?

IllegalArgumentException is an unchecked Java exception (a.k.a. runtime exception). It derives from RuntimeException, which is the base class for all unchecked exceptions in Java.

Here’s the inheritance hierarchy of IllegalArgumentException:

Throwable (java.lang)
    Exception (java.lang)
        RuntimeException (java.lang)
            IllegalArgumentException (java.lang)

Because IllegalArgumentException is an unchecked exception, the Java compiler doesn’t force you to catch it. Neither do you need to declare this exception in your method declaration’s throws clause. It’s perfectly fine to catch IllegalArgumentException, but if you don’t, the compiler will not generate any errors.

What Java exceptions are related to IllegalArgumentException?

IllegalArgumentException is the most generic in a group of exceptions that indicate incorrect input data. It has a lot of inheritors in the JDK that represent more specific input errors. These include:

  • IllegalFormatException and its inheritors that are thrown when illegal syntax or format specifiers are detected in a format string.
  • InvalidPathException that is thrown when a string that is expected to represent a file system path can’t be converted into an object of type Path because it contains invalid characters.
  • NumberFormatException that is thrown when an attempt to convert a string to a numeric type fails because the string has an incompatible format.

In OpenJDK 17, the full list of exceptions derived from IllegalArgumentException is as follows:

IllegalArgumentException (java.lang)
    CSVParseException (com.sun.tools.jdeprscan)
    IllegalChannelGroupException (java.nio.channels)
    IllegalCharsetNameException (java.nio.charset)
    IllegalFormatException (java.util)
        DuplicateFormatFlagsException (java.util)
        FormatFlagsConversionMismatchException (java.util)
        IllegalFormatArgumentIndexException (java.util)
        IllegalFormatCodePointException (java.util)
        IllegalFormatConversionException (java.util)
        IllegalFormatFlagsException (java.util)
        IllegalFormatPrecisionException (java.util)
        IllegalFormatWidthException (java.util)
        MissingFormatArgumentException (java.util)
        MissingFormatWidthException (java.util)
        UnknownFormatConversionException (java.util)
        UnknownFormatFlagsException (java.util)
    IllegalSelectorException (java.nio.channels)
    IllegalThreadStateException (java.lang)
    InvalidKeyException (javax.management.openmbean)
    InvalidOpenTypeException (javax.management.openmbean)
    InvalidParameterException (java.security)
    InvalidPathException (java.nio.file)
    InvalidStreamException (com.sun.nio.sctp)
    KeyAlreadyExistsException (javax.management.openmbean)
    NumberFormatException (java.lang)
    PatternSyntaxException (java.util.regex)
    ProviderMismatchException (java.nio.file)
    SAGetoptException (sun.jvm.hotspot)
    UnresolvedAddressException (java.nio.channels)
    UnsupportedAddressTypeException (java.nio.channels)
    UnsupportedCharsetException (java.nio.charset)

How to catch IllegalArgumentException in Java

Since IllegalArgumentException is an unchecked exception, you don’t have to handle it in your code: Java will let you compile just fine.

In many cases, instead of trying to catch IllegalArgumentException, you can simply check that a value falls in the expected range before passing it to a method.

If you do choose to handle IllegalArgumentException with a try/catch block, depending on your business logic, you may want to substitute the offending argument with a default value, or modify the offending argument to make it fall inside the expected range.

When you handle IllegalArgumentException, note that it doesn’t provide any specialized methods other than those inherited from RuntimeException and ultimately from Throwable. When catching and handling an IllegalArgumentException, as with any other Java exception, you most commonly use standard Throwable methods like getMessage(), getLocalizedMessage(), getCause(), and printStackTrace().

Let’s look at an example of how you can get an IllegalArgumentException when working with common Java library code. When handling it, we’ll log the exception and retry with a default value instead of an incorrect argument.

IllegalArgumentException example 1: Unrecognized log level in Java logging API

When you work with Java’s core logging API defined in module java.util.logging, you either use predefined log levels (SEVERE, WARNING, FINE, FINER, etc.) or provide a string that is then parsed to match a known log level or an integer:

String logLevel = "SEVERE";
LOGGER.log(Level.parse(logLevel), "Processing {0} entries in a list", list.size());

If you make a typo specifying a log level in a string — for example, pass in the string "SEVER" instead of "SEVERE"the logger will not be able to parse the level and will throw IllegalArgumentException:

Exception in thread "main" java.lang.IllegalArgumentException: Bad level "SEVER"
    at java.logging/java.util.logging.Level.parse(Level.java:527)
    at com.lightrun.exceptions.Main.main(Main.java:29)

Process finished with exit code 1

You could prevent this exception altogether if you avoid parsing and stick to the predefined log levels. However, if for some reason you need to keep parsing log levels, one option would be to wrap logging in the try/catch block, and when you catch IllegalArgumentException, recover by rolling back to a default logging level:

String logLevel = "SEVER";
try {
    LOGGER.log(Level.parse(logLevel), "Processing {0} entries in a list", list.size());
} catch (IllegalArgumentException e) {
    Level defaultLogLevel = Level.WARNING;
    LOGGER.log(Level.INFO, "Provided invalid log level {0}, defaulting to INFO", logLevel);
    LOGGER.log(defaultLogLevel, "Processing {0} entries in a list", list.size());
}

IllegalArgumentException example 2: Randomizer

Java’s standard random number generator, java.util.Random, has a method nextInt(int bound) that generates a random number in a range from 0 (inclusive) to the upper bound expressed by its bound parameter (exclusive). Since the parameter is of type int, you can pass 0 or a negative integer without breaking any type system constraints. However, in the context of this method, passing any number smaller than 1 does not make sense: the generator can’t generate a random number in the range from 0 (inclusive) to 0 (exclusive), nor will it generate a number in a negative range. Consider the following method:

public static int randomize(ArrayList<Integer> list){
    Random randomGenerator = new Random();
    return randomGenerator.nextInt(list.size());
}

This method takes a list and returns a random number from 0 to the length of the list. Now, if the list has any items in it, this code will work fine, but what if the list is empty?

ArrayList<Integer> integerList = getListFromElsewhere();
System.out.printf("The size of this list is %d%n", integerList.size());
int randomInteger = randomize(integerList);

Let’s see what happens when we run this code:

The size of this list is 0
Exception in thread "main" java.lang.IllegalArgumentException: bound must be positive
    at java.base/java.util.Random.nextInt(Random.java:322)
    at com.lightrun.exceptions.RandomSample.randomize(RandomSample.java:35)
    at com.lightrun.exceptions.RandomSample.illegalArgumentExceptionWithRandomizer(RandomSample.java:15)
    at com.lightrun.exceptions.Main.main(Main.java:11)

Process finished with exit code 1

As you can see, nextInt() throws an exception that remains unhandled, and the program exits.

Suppose you’re unable to modify the randomize() method. How can you handle this on the call site?

In principle, you could throw a new runtime exception, specify the IllegalArgumentException coming from nextInt() as its cause, and communicate up the call stack that the provided list should not be empty:

int randomInteger;
try {
    randomInteger = randomize(integerList);
} catch (IllegalArgumentException illegalArgumentException) {
    String description = String.join("\n",
            "The provided list is empty (size = 0).",
            "The randomizer can't generate a random number between 0 and 0.",
            "In order to use the size of a list as the upper bound for generating random numbers,",
            "please provide a longer list."
            );
    throw new RuntimeException(description, illegalArgumentException) {
    };
}

Another solution, and probably a more practical one, would be to check if the supplied list is empty, and if so, return a fixed value instead of calling the randomize() method:

ArrayList<Integer> integerList = getListFromElsewhere();
int randomInteger = integerList.size() > 0 ? randomize(integerList) : 1;

When and how to throw IllegalArgumentException in Java

You normally throw an IllegalArgumentException when validating input parameters passed into a Java method and you need to be more strict than the type system allows.

For example, if your method accepts an integer parameter that it uses to express a percentage, then you probably need to make sure that in order to make sense, the value of that parameter is between 0 and 100. If the value falls out of that range, you can throw an IllegalArgumentException:

public static int getAbsoluteEstimateFromPercentage(double percentOfTotal) {

    int totalPopulation = 143_680_117;

    if (percentOfTotal < 0 || percentOfTotal > 100) {
        throw new IllegalArgumentException("Percentage of total should be between 0 and 100, but was %f".formatted(percentOfTotal));
    }

    return (int) Math.round(totalPopulation * (percentOfTotal * 0.01));
}

It’s important to provide meaningful exception messages to make troubleshooting easier. This is why instead of throwing an IllegalArgumentException with an empty value, we provide a message that:

  • Defines the valid range of parameter values.
  • Includes the exact value that was out of the valid range.

When you throw an IllegalArgumentException in your method, you don’t have to add it to the method’s throws clause because it’s unchecked. However, many developers tend to add selected unchecked exceptions to the throws clause anyway for documentation purposes:

public static int getAbsoluteEstimateFromPercentage(double percentOfTotal) throws IllegalArgumentException {}

Even if you do add IllegalArgumentException to throws, callers of your method will not be obliged to handle it.

An alternative way of documenting important unchecked exceptions is using the @throws Javadoc documentation tag. In fact, the Oracle guidelines on using Javadoc comments claim that including unchecked exceptions such as IllegalArgumentException into a method’s throws class is a bad programming practice and recommends using the @throws documentation tag instead:

/**
 * @param percentOfTotal Percentage of total
 * @return A rough estimate of the absolute number resulting from taking a percentage of total
 * @throws IllegalArgumentException if percentage is outside the range of 0..100
 */
public static int getAbsoluteEstimateFromPercentage(double percentOfTotal) {

How to avoid IllegalArgumentException

Because IllegalArgumentException is an unchecked exception, your IDE or Java code analysis tool will probably not help you see if code that you’re calling will throw this exception before you run your application.

What your IDE can sometimes do for you is detect if the argument that you pass to a library method is out of range for this method. If you’re using IntelliJ IDEA for Java development, it comes with a set of annotations for JDK methods: additional metadata that helps clarify how these methods should be used. Specifically, there’s a @Range annotation that describes the acceptable range of values for a method, and if you’re writing code that violates that range, the IDE will let you know.

For example, the nextInt() method of JDK’s Random type is annotated with @Range, and if you invoke the Quick Documentation popup on that method, you’ll see that it tells you about the acceptable range:

Range of nextInt() documented in IntelliJ IDEA

Even if you go ahead and pass a value to nextInt() that is known to be out of range for this method, IntelliJ IDEA will display a highlight in the code editor to warn you about the issue:

IntelliJ IDEA warns about an invalid value passed to nextInt()

However, if you’re using a method that is not annotated like this, don’t rely on your IDE: you’re on your own.

In general, when calling library methods, it’s a good practice to take note of throws clauses and @throws Javadoc documentation tags. In many cases, library developers use either or both of these tools to document why their methods could throw IllegalArgumentException.

Production debugging isn’t scary with Lightrun

Properly handling exceptions is one thing that you as a developer can do to ensure a smooth ride in production for your Java applications. Still, let’s face it: any non-trivial application will have bugs. You’re lucky if you can reproduce a bug in a local environment, debug and happily push a verified fix.

What if you can’t? Debugging remotely is tricky: you need to rely on existing logging, repeatedly redeploy updates with more logs and attempted fixes, and you’re even unable to set a proper breakpoint because you can’t afford to halt a production environment.

Take a look at Lightrun: our next-gen remote debugger for your production environment. With Lightrun, you can inject logs without changing code or redeploying, and add snapshots: breakpoints that don’t stop your production application. Lightrun supports Java, .NET, Python and Node.js applications, integrates with IntelliJ IDEA and VS Code. Set up a Lightrun account and check for yourself!

Share

It’s Really not that Complicated.

You can actually understand what’s going on inside your live applications.

Try Lightrun’s Playground

Lets Talk!

Looking for more information about Lightrun and debugging?
We’d love to hear from you!
Drop us a line and we’ll get back to you shortly.

By clicking Submit I agree to Lightrun’s Terms of Use.
Processing will be done in accordance to Lightrun’s Privacy Policy.