Home » Core Java » Java Optional Parameters

About Firouzeh hejazi

Firouzeh hejazi
A self-motivated and hard-working developer with more than 4 years of extensive experience in developing software in java platforms. A multi-skilled and problem-solving engineer who has participated in implementing a range of applications in different roles. Possess a MSc in Knowledge Engineering and Decision Science.

Java Optional Parameters

In this post, we feature a comprehensive article about the Java Optional Parameters. When you design a method in a Java class, some parameters may be optional for its execution.

1. Java Optional Parameters

You can tackle Java optional parameters in method in several different ways. We will see them in the following:

1.1 Mutability with accessors

Using standard getters and setters is a simple way to work with an object that has optional instance parameters. We use a default constructor with mandatory parameters to create the object then invoke the setter methods to set the value of each optional parameter as needed. We can set the default values for parameters within a constructor, if necessary:

ChemicalPotion.java
public class ChemicalPotion {

    private String name;    // required
    private int materialA;  

    public ChemicalPotion (String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getMaterialA() {
        return materialA;
    }

    public void setMaterialA(int materialA) {
        this.materialA= materialA;
    }

    // other getters and setters
}

This approach is the ubiquitous JavaBeans pattern and is likely the simplest strategy available for working with optional parameters. There are, unfortunately, serious drawbacks to use this approach, especially if thread safety is a concern. The use of this pattern requires that the object is mutable since we can change it after its creation.

Since the creation of the instance and setting of its state are decoupled and do not occur atomically, it’s possible that the instance could be used before it’s in a valid state. In a sense, we’re splitting the construction of the object over multiple calls.

You can consider this pattern when thread safety and creating a robust API isn’t a primary concern.

1.2 @Nullable annotation

You can just pass the null. It’s a simple solution which doesn’t require any extra work. You don’t have any object which is required as one of method’s parameters? No problem. Just pass the null and the compiler is happy.

The issue here is readability. How does the programmer who calls a method knows if he can safely pass the null? For which parameters nulls are acceptable and which are mandatory?

To make it clear that the null is a valid input, you can use a @Nullable annotation.

passing the null
Student createStudent(String name, @Nullable Email email) {
   // ...
}

Although null passing approach looks simple, the issue with it is that it can easily get out of control. The team may quickly start overusing it and make the code base hard to maintain with plenty of null check conditions.

There are other options, though.

1.3 Optional lists

Instead of null, we can sometime create an empty representation of a class. Think about Java collections. If a method accepts a list or a map, you should never use nulls as the input.

An empty collection is always better than null because in majority of cases it doesn’t require any special treatment.

You may wonder why you should waste memory to create empty collections. After all, null doesn’t cost you anything.

Your doubts are justified. Fortunately, there’s a simple solution.

You shouldn’t create a new instance of a collection whenever you need an empty representative. Reuse the same instance across the code base. Java already has empty instances of all collections which you can use. You’ll find them in the Collections utility class.

passing a List
Student createStudent(String name, List courses) {
   // ...
}
.
.
.
import java.util.Collections;
// ...
createStudent("John", Collections.emptyList());

1.4 Null object pattern

The Null object is a special instance of a class which represents missing value. If some method expects an object as a parameter, you can always pass the Null object representation without worry it will cause an unexpected exception at the runtime.

You can implement the Null object pattern in two ways.

For simple value objects, a default instance with predefined values assigned to properties is enough. Usually, you expose this Null object as a constant so you can reuse it multiple times. For instance:

Student.java
public class Student {
 
   public static final User EMPTY = new Student("", 
                                    Collections.emptyList());
 
   private final String name;
   private final List courses;
 
   public Student(String name, List courses) {
       Objects.requireNonNull(name);
       Objects.requireNonNull(courses);
       this.name = name;
       this.courses = courses;
   }
 
   // ...
 
}

If your Null object also needs to mimic some behavior exposed via methods, a simple instance may not work. In that case, you should extend the class and override such methods.

Here is an example which extends the previous one:

AnonymousStudnet.java
public class AnonymousStudnet extends Student{
 
   public static final AnonymousStudnet INSTANCE = new AnonymousStudnet 
                                                   ();
 
   private AnonymousStudnet() {
      super("", Collections.emptyList());
   }
 
   @Override
   public void changeName(String newName) {
       throw new AuthenticationException("Only authenticated student can 
       change the name");
   }
 
}

A dedicated Null object class allows you to put many corner cases in a single place which makes maintenance much more pleasant.

1.5 Method overloading

The idea here is that we start with a method that only takes the required parameters. We provide an additional method which takes a single optional parameter. We then provide yet another method which takes two of these parameters, and so on.

The methods which take fewer parameters supply default values for the more verbose signatures.

With this approach, you don’t have to expect that the caller will provide default values for optional parameters. You pass the defaults on your own inside overloaded method. In other words, you hide the default values for optional parameters from method’s callers.

Method Overloading
Student createStudent(String name) {
   this.createStudent(name, Email.EMPTY);
}
 
Student createStudent(String name, Email email) {
   Objects.requireNonNull(name);
   Objects.requireNonNull(rights);
   // ...
}

See the example below:

ChemicalPotion.java
public class ChemicalPotion {

    private String name;    // required
    private int materialA;   // in mg
    private int materialB;   // in mg
    private int materialC;    // in mg

// instance fields

public ChemicalPotion(String name) {
    this(name, 0);
}

public ChemicalPotion(String name, int materialA) {
    this(name, materialA, 0);
}

public ChemicalPotion(String name, int materialA, int materialB) {
    this(name, materialA, materialB, 0);
}

public ChemicalPotion(String name, int materialA, int materialB, int materialC) {
    this.name = name;
    this.materialA= materialA;
    this.materialB= materialB;
    this.materialC= materialC;
}
}

The simplicity and familiarity of the method overloading approach make it a good choice for use cases with a small number of optional parameters.

The main downside of using this approach is that it does not scale well – as the number of parameters increases. This only gets worse with the fact that our optional parameters are of the same type. Clients could easily order the parameters wrongly – such a mistake would not be noticed by the compiler and would likely result in a subtle bug at runtime.

1.6 Parameter Object pattern

Most of developers agree that when the list of method parameters grows too long it become hard to read. Usually, you handle the issue with the Parameter Object pattern. The parameter object is a named container class which groups all method parameters.

Now the question is: Does it solve the problem of optional method parameters?

No. It doesn’t. It just moves the problem to the constructor of the parameter object.

In the following section we will discribe the Optional constructor parameters.

2. Optional constructor parameters

In the perspective of the problem with optional parameters, simple constructors don’t differ from regular member methods. You can successfully use all the techniques we’ve already discussed also with constructors.

However, when the list of constructor parameters is getting longer and many of them are optional parameters, applying constructor overloading may seem cumbersome. Lets see what is Builder pattern.

2.1 Builder pattern

One of the main advantages of the builder pattern is that it scales well with large numbers of optional and mandatory parameters.

If you created a constructors to cover all possible combination with optional parameters, you would end up with a quite overwhelming list.

In our example here, we require the mandatory parameter in the constructor of the builder. We expose all of the optional parameters in the rest of the builder’s API.

You usually implement the builder as an inner class of the class it suppose to build. That way, both classes have access to their private members. Take a look at the following example:

StudentProfile.java
class StudentProfile {
 
   // required field
   private final String name;

   // optional fields
   private final String email;
   private final String birthDate;
   private final String githubUrl;
 
   private StudentProfile (Builder builder) {
       Objects.requireNonNull(builder.name);
       name = builder.name;
       email= builder.email;
       birthDate= builder.birthDate;
       githubUrl = builder.githubUrl;
   }
 
   public static Builder newBuilder() {
       return new Builder();
   }
 
   static final class Builder {
       
       private String name;
       private String email;
       private String birthDate;
       private String githubUrl;
 
       private Builder() {}
 
       public StudentProfile.Builder withName(String val) {
           name = val;
           return this;
       }
 
       public StudentProfile.Builder withEmail(String val) {
           email= val;
           return this;
       }
 
       public StudentProfile.Builder withBirthDate(String val) {
           birthDate= val;
           return this;
       }
 
       public StudentProfile.Builder withGithubUrl(String val) {
           githubUrl = val;
           return this;
       }
 
       public StudentProfile build() {
           return new StudentProfile(this);
       }
   }
}

Another advantage is that it’s much more difficult to make a mistake when setting values for optional parameters. We have explicit methods for each optional parameter, and we don’t expose callers to bugs that can arise due to calling methods with parameters that are in the wrong order.

Instead of a public constructor, we only expose one single static factory method for the inner builder class. The private constructor (which the builder calls in the build() method) uses a builder instance to assign all fields and verifies if all required values are present.

The client code of that builder which sets only a selected optional parameter may look as follows:

Builder code for StudentProfile
StudentProfile.newBuilder()
       .withName("John")
       .withEmail("john.uni@gmail.com")
       .build();

With the builder, you can create all possible combinations with optional parameters of the object.

And also for the ChemicalPotion example we can have something like this:

Builder code for ChemicalPotion
ChemicalPotionWithBuildermyPotion 
  = new ChemicalPotionWithBuilder.ChemicalPotionBuilder("Energetic Potion")
    .withMaterialA(5000)
    .withMaterialB(2000)
    .withMaterialC(3000)
    .build();

2.2 Compile time safe class builder

Unfortunately, just by look at the methods of the builder from the previous paragraph you can’t really tell which parameters are optional and which are required. What is more, without knowing you can omit the required parameters by accident.

Check out the following example of the incorrectly used builder:

Builder code for StudentProfile
StudentProfile.newBuilder()
       .withEmail("john.uni@gmail.com")
       .withBirthDate("2012-01-05")
       .build();

The compiler won’t report any error. You’ll realize the problem with the missing required parameter only at the runtime.

You need to slightly modify the builder factory method so that you can call it only with required parameters and left builder methods only for optional parameters.

Here’s all you have to change:

class StudentProfile {
    
   // ...
 
   public static Builder newBuilder(String name) {
      return new Builder(name);
   }
 
   public static final class Builder {
 
      private final String name;
      // ...
 
      private Builder(String name) {
          this.name = name;
      }
 
      // ...
 
   }
}

You may think that builders require hell of a lot of code. Don’t worry. You don’t have to type all that code on your own. All popular Java IDEs have plugins which allow to generate class builders. IntelliJ users can check the InnerBuilder plugin.

3. Conclusion

In this article, we’ve looked at a variety of strategies for working with optional parameters in Java, such as method overloading, the builder pattern, and the strategy of allowing callers to supply null values.

We highlighted the relative strengths and weaknesses of each strategy and provided usage for each.

4. ِDownload the source code

Download
You can download the full source code of this example here: Java Optional Parameters

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

 

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

 

and many more ....

 

Receive Java & Developer job alerts in your Area

 

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
Notify of