Wednesday, April 15, 2015

Validating method parameters in Java

Validating input arguments is a tedious task. Regardless of the programming language used, method parameters must be checked to ensure that the allowed values are used. In some cases, this process includes trimming additional spaces from string parameters before validation rules can be applied to the input.

The following post describes a validation strategy for method parameters that uses utility methods from the Java API, the Apache Commons Lang API and the Google Guava library.

The strategy consists on creating a defensive copy of a method's input parameters, simplifying the input whenever possible and improving code security by removing unnecessary update operations from collections that don't need to support mutation. Then, validation rules are applied to the copies, interrupting the execution of the method when an invalid value is found and providing default values to optional parameters. An example method is described below to illustrate this strategy.

The example method receives two parameters. The first parameter is always required and the validation rules can vary from one case to another. When the method fails to validate the value passed in the required parameter, two unchecked exceptions are thrown: a NullPointerException to indicate that a null value was passed to the method and a IllegalArgumentException to indicate other invalid values. In every case, a custom error message is provided with the exception. The second parameter is optional and a default value is used when none is provided.

Three types of parameters are covered using similar validation rules and producing the same errors described above. First, a method receiving String parameters is presented. The second method receives List parameters, and finally, a third method is presented that receives Map parameters. Several variations of the methods are described covering common usage scenarios for each type.

String parameters


The first method receives a required parameter that accepts empty values and a second optional parameter where null and empty values are replaced by a default value:


Method parameters are declared final, which is a good practice to prevent your methods from overriding the parameters they receive. This is especially important when you want to preserve the values of the attributes of an object passed to your method.

In addition, the optional parameter is marked with the @Nullable annotation to make clear that this parameter accepts null values. This will help other developers to quickly understand the values accepted by your method, but it also serves as a hint for code analyzers like FindBugs. Include the following import in your class to get access to the @Nullable annotation:

import javax.annotation.Nullable;

Also, include the following imports in your class in order to get access to the utility methods:

import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.commons.lang3.StringUtils.trim;
import static org.apache.commons.lang3.StringUtils.trimToNull;

The methods trim and trimToNull remove spaces from both ends of a string. The main difference is in how empty strings are treated. While trim preserves empty strings, trimToNull replaces them with null.

A copy of the required parameter is created with checkNotNull, which also checks that the value is not null. The second parameter of this method is the error message provided with the NullPointerException when a null value is entered.

In the case of the optional parameter, the method fromNullable is used to create a non-null reference and if a valid value is present, a copy is created. Otherwise, the specified default is used.

In this strategy, the methods trim and trimToNull serve to reduce the input to its equivalent simplest form, which is commonly referred as the canonical form. Canonicalization occurs before validation, allowing methods to operate on the canonicalized, validated version of the parameters.

When the empty string is not an allowed value of the required parameter, adding the following check will throw an IllegalArgumentException if the provided value is empty. The second parameter of the method checkArgument is the error message of the IllegalArgumentException:

checkArgument(!required2.isEmpty(), "Empty string is not allowed");

Add the following import to your class to get access to this method:

import static com.google.common.base.Preconditions.checkArgument;


List parameters


The same strategy can be extended to validate parameters with other commonly used types, in particular with generic lists:


The same method checkNotNull can be used to check that the value of the input list is not null. However, depending on the needs of your application, there are several options that you can use to create a copy of the list.

While Java provides methods to make your collections unmodifiable, you can also use the Guava's immutable collections to create lists where the items stored in the list cannot be modified. Otherwise, if you need to make changes to your list, then you can rely on the standard Java List.

However, always consider using immutable collections (Java or Guava's implementation) because they are safer than the mutable ones, avoiding synchronization problems and keeping memory usage at a minimum.

Include the following additional condition in case that you need to check that your required list is not empty:

checkArgument(!required2.isEmpty(), "Empty list is not allowed");


Map parameters


Validating generic map parameters is quite similar to validating lists:


The same options exist to create copies of the maps. Similarly to the case of the list, the required map can be validated to avoid null values:

checkArgument(!required2.isEmpty(), "Empty map is not allowed");


Conclusions


Despite method's argument validation is a common need of all applications, there is no simple way to check if a parameter passed to a method is valid, and most solutions require significant effort to write validation tests. In this post, a strategy is explored that combines utility methods from the Java API, the Apache Commons Lang API and the Google Guava library. The provided examples serve to the purpose of illustrating common usage cases where the strategy can help to write better code, which is my objective with this blog.

A complete working example is available on my GitHub account under the Apache License, version 2.0. Convenient test cases are provided with JUnit and Maven.

No comments:

Post a Comment