Why and how to use Static Factory methods instead of public constructors in Java

One of the most important aspects of using an object-oriented programming language is how and when programmers decide to create and destroy objects in their programs. Making a good decision on this is a subtle art that comes with programming experience. In this blog, I will take you through the steps of creating Static Factory methods to create objects in Java. I will also talk about the various advantages of taking this route instead of the more common approach of using public constructors for object creation. One important thing to note here is that this “Static Factory method” is different from the “Factory method” pattern with the latter being a type of design pattern.

Whenever we want any client/user to get an instance of a class, we almost always create a public constructor of that class (with or without parameters) and expose it to allow the client to create objects from outside of the class by calling it with the “new” keyword. Most of the time we would not want the objects to be created from outside the class. There exists a way by which we can achieve that – using a “Static Factory method”. For this, we need to create a “public static method” inside the class, with a meaningful name, and return the instance that the client/user needs.

Consider the code below:

package staticFactoryMethodsOverConstructors;

public class Player {

    int age;
    String playerName;
    String game;

    public Player(int age, String playerName){
        this.age = age;
        this.playerName = playerName;
        this.game = "Cricket";
    }

    @Override
    public String toString() {
    return "Player1{" +
            "age=" + age +
            ", playerName='" + playerName + '\'' +
            ", game='" + game + '\'' +
            '}';
 }
}
package staticFactoryMethodsOverConstructors;

public class Main {

    public static void main(String[] args) {
        var firstPlayer = new Player(22, "Sam");
        System.out.println(firstPlayer);
    }

}

Here, the class “Player” has three instance variables (age, playerName, and game) and a public constructor to initialize those variables. Inside the constructor, the “game” variable is initialized to “Cricket” by default. An object, with reference “firstPlayer”, is created from another class called “Main”. The values “22” and “Sam” are sent as parameters to the constructor to create this “firstPlayer” object.

There is a problem in taking this approach. Consider a scenario, where the client would like to only send the name and age, but the game variable needs to be initialized to “Football” and not “Cricket” by default. First of all, we will not be able to create another constructor with the same parameter types, with the same number of parameters, and in the same order, as it is not allowed in Java and it will throw compile error. One way to overcome this problem is to create another parameterized constructor, with three parameters (age, playerName, and dummy) and ignore the dummy variable in the constructor body, like below:

public Player(int age, String playerName, int dummy){
    this.age = age;
    this.playerName = playerName;
    this.game = "Football";
}

Or change the order of the parameters. Both of these will change the signature, are kind of hacks and obviously, are not good approaches. Using the “Static Factory method”, this type of problem can be easily eliminated. Let’s see in the code snippet below how we can create objects using the Static Factory method and remove this issue.

package staticFactoryMethodsOverConstructors;

public class Player {

    int age;
    String playerName;
    String game;

    private Player(int age, String playerName, String game){
        this.age = age;
        this.playerName = playerName;
        this.game = game;
    }

    public static Player getCricketPlayer(int age, String playerName){
        return new Player(age, playerName, "Cricket");
    }

    public static Player getFootballPlayer(int age, String playerName){
        return new Player(age, playerName, "Football");
    }

    @Override
    public String toString() {
        return "Player{" +
                "age=" + age +
                ", playerName='" + playerName + '\'' +
                ", game='" + game + '\'' +
                '}';
    }
}
package staticFactoryMethodsOverConstructors;

public class Main {

    public static void main(String[] args) {
        var cricketPlayer = Player.getCricketPlayer(22, "Sam");
        System.out.println(cricketPlayer);

        var footballPlayer = Player.getFootballPlayer(22, "Sam");
        System.out.println(footballPlayer);
    }
}

In this code snippet as you can see – the “Player” class has two static methods – “getCricketPlayer” and “getFootballPlayer”. The “getCricketPlayer” method takes the age and playerName as arguments, and creates an object by passing their values to the private “Player” constructor, along with the string value “Cricket”. Similarly, the “getFootballPlayer” performs the same function but by sending the string value “Football”, instead of “Cricket”. When the program is run, the output we get is:

Player{age=22, playerName=’Sam’, game=’Cricket’}
Player{age=22, playerName=’Sam’, game=’Football’}

According to its requirement, now the client can call each of these methods by passing the same number of parameters and in the same order. In return, it will receive an appropriate object of the Player class.

Let’s talk about the various advantages of taking the approach over using constructors:

1) Unlike Constructors, these methods have names
We are not entitled to change the names of the constructors (as they should bear the same name as that of their respective classes) and they cannot be named to something else that is meaningful concerning the objects being created. In cases where the user of the API needs to get different objects using constructors, there is no way that they will get to know about the objects being returned from the names. A “Static Factory method” removes this problem by enabling us to use meaningful names for the method which will tell us about the object being returned by that method. For example, in the code above, the methods “getCricketPlayer” and “getFootballPlayer” are doing exactly that. A good naming practice for static factory methods is to use names like “of”, “from”, “instance”, “getInstance”, “newInstance”, “get<Type>”, “valueOf”, “create”, “new<Type>”, “type” etc.

2) Unlike Constructors, these methods don’t need to create new objects when invoked
We know that whenever we are calling constructors, along with the “new” keyword, we are creating new objects. It is not always required or recommended to create new objects. Sometimes what the user needs is just an existing/equivalent object and not a new/duplicate one. A static factory method helps us to achieve that. Sometimes this approach improves the program performance since it can use pre-constructed instances. It can request equivalent objects rather than create new ones. For objects which are expensive to create, this approach is highly useful and performance-friendly. A static factory method can return the same object from repeated invocations and it thus enables the class to have control of its instances at any point in time. This approach helps these classes to be singleton or non-instantiable.

3) Static Factory methods can return an object of any subtype of the return type
A Static Factory Method can return different subtype objects of the declared return type. This is a very important feature that it provides. A client or user will be least interested to know about “how” an object is created as long as it is getting the expected object. Hence, by encapsulating this object creation activity, the implementation can be hidden from the user/client. At a later point in time, if we want to change the implementation, we can do that without letting the clients/users, invoking the object creation, deal with the change. Observe the code below:

package staticFactoryMethodsOverConstructors;

public class BoxFactory {

    public static Box newBox(String boxType) {
        return switch (boxType) {
            case "Wooden" -> new WoodenBox();
            case "Plastic" -> new PlasticBox();
            default -> throw new IllegalArgumentException("Unsupported box type: " + boxType);
        };
    }

}

class Box {
    double width = 0;
    double height = 0;

    @Override
    public String toString() {
        return "Box{" +
                "width=" + width +
                ", height=" + height + "}";
    }
}

class WoodenBox extends Box {
    double width = 2.2;
    double height = 3.3;

    @Override
    public String toString() {
        return "WoodenBox{" +
                "width=" + width +
                ", height=" + height + "}";
    }
}

class PlasticBox extends Box {
    double width = 1.4;
    double height = 1.6;

    @Override
    public String toString() {
        return "PlasticBox{" +
                "width=" + width +
                ", height=" + height + "}";
    }
}

Here, a simple “BoxFactory” class is created that is having the static factory method named “newBox()”. The method takes a String parameter and based on its value (and if the value is a valid one) returns the relevant object of either of the class type – “WoodenBox” or “PlasticBox”. Since both “WoodenBox” and “PlasticBox” are subclasses of the “Box” class, the superclass “Box” itself can be used as the return type of the method.

package staticFactoryMethodsOverConstructors;

public class Main3 {

    public static void main(String[] args) {

        Box plasticBox = BoxFactory.newBox("Plastic");
        System.out.println(plasticBox);

    }
}

The client/user just needs to call the static factory method with the appropriate parameter and it will receive the expected “PlasticBox” object according to the passed parameter “Plastic”. It need not know about the inner details of the static factory method “newBox()”.

4) They can return an object whose class can vary from call to call as a function of input parameters
In the previous example code, you could see that we have passed the “Plastic” string as the input parameter to the static factory method. We could also have passed “Wooden” as a string input parameter to it. So, depending on what the client/user is passing as an input parameter while invoking the static factory method, it will receive an object whose class can vary from call to call. For passing “Plastic” as a parameter, it has received a “PlasticBox” object. If it would have passed “Wooden” as the input parameter, it would have received a “WoodenBox” object. Here lies the beauty of a static factory method over a constructor.

5) These methods can return an object whose class need not exist when the class containing these methods is written
This becomes specifically important during the implementation of the “Service-Provider” frameworks that usually contain service interfaces and provider registrations. One good example is the JDBC API. Consider the code below:

package staticFactoryMethodsOverConstructors;

public class CityServiceFactory {

    public static CityService getService() {
        try {
            return (CityService) Class.forName(System.getProperty("CityServiceType")).newInstance();
        } catch (Throwable throwable) {
            throw new Error(throwable);
        }
    }
}

interface CityService {
    void provideService();
}

Here, you can see that I have created a class named “CityServiceFactory” which contains the static factory method “getService()”. At runtime, this method creates and returns an instance of a class whose name is stored as a value for the system property key named “CityServiceType”, but it has no idea what actual class it is. The returned instance implements “CityService” which is an interface having the unimplemented method “provideService()” and that’s all that the static factory method “getService()” knows. When the code for the “CityServiceFactory” class is written, the service classes implementing the “CityService” interface may or may not exist. Hence, the user/client need not know or care about whether the individual services have been implemented or not. They just need to set a system property containing the name of the implementation they want to use.

This brings us to the end of this blog post. Hope you have liked it 🙂

If you want to read more on some interesting Java topics, checkout the below:

https://www.sumondey.com/alternative-approaches-to-using-if-else-and-switch-in-code/

https://www.sumondey.com/serialization-and-deserialization-using-jackson-objectmapper/

https://www.sumondey.com/what-your-automation-code-will-look-like-with-the-latest-java15-features/

https://www.sumondey.com/25-cool-java-things-you-may-or-may-not-have-come-across/

https://www.sumondey.com/fillo/

https://www.sumondey.com/lombok-annotations/