Builder v AllArgsConstructor – Comparison between Object Creation Patterns

In various scenarios, POJOs need to be defined with too many fields. Initializing such fields can be done in multiple ways as mentioned below –

  • Default constructor and Setters
  • All arguments Constructor
  • Default initialization in class body
  • Static factory methods
  • Builders

Though each of them have their own advantages and disadvantages, for now, let us consider a comparison between constructor with all arguments and builder. The choice of comparison is arising due to the reason that both of these are very easily implementable  using the Lombok library.  Lombok library helps in implementing both the patterns just using simple annotations for the type Р@AllArgsConstructor and @Builder. Apart from them, setter based initialization is cumbersome and default initialization does not seem appropriate in most of the cases. Static factory methods is not supported by Lombok and thus needs effort to be implemented.

Advantages of all argument constructor:

  • Object is initialized in one line of code.
  • Consumer cannot omit initialization of any member variable.
  • Better performance than any other option.
  • Object either does not exist or exists fully initialized, no tri-state.
  • Members can be made final and thus making the object immutable.

Disadvantages of all argument constructor:

  • Too many parameters for the constructor – mostly same type gets repeated and one clearly needs to understand and remember the parameter sequence.
  • If source / javadoc are not published for the implementation, the IDEs provide identical names like aLong1, aLong2 etc and the programmer gets confused while creating such objects.
  • Adding new member to the class breaks all existing consumers.

Advantages of Builder:

  • Easy to initialize, without requiring to remember the names, types and sequence of members, even when javadoc / sources are unavailable.
  • Intent to skip initializing a variable is achieved without clutter in code.
  • Adding new members to the class will not break existing implementations.

Disadvantages of Builder:

  • Contrary to the second advantage, one can easily forget to initialize any members.
  • Cost on performance and resource consumption. Builders involve creation of new Builder objects, multiple calls to setters in the Builder, then call the build() method to create the object, discard the builder which was created and return the actual object. Builder objects are created just to facilitate creation of actual objects making it memory intensive and numerous setter calls would cost on the CPU cycles.
  • Consumer can inadvertently create objects which are half initialized / uninitialized – might cause issues in the runtime.

Even though with multiple disadvantages, Effective Java still recommends a builder over constructor when we encounter a bigger parameter list. As the discussion puts focus on performance and resource utilization, let us try to evaluate both approaches with an example code.

Consider the object as given below:

 @Data
 @AllArgsConstructor
 @Builder
 static class Vehicle {
 private short wheels;
 private String brand;
 private String model;
 private int engineCapacity;
 private int price;
 private short seatingCapacity;
 private FuelType fuelType;
 private String regNo;
 private String color;
 private TransmissionType transmissionType;
 private VehicleType vehicleType;
 private UsageType usageType;
 }

 

This has 12 members and all argument constructor would be too confusing for implementation. Builder would be consumer friendly, trading off the resource consumption and performance time. Below code shall be considered to evaluate the performance and analyze the results with Java’s own jvisualvm profiler.

public static void main(String[] args) throws InterruptedException {
 List<Vehicle> vehicleList = new ArrayList<Vehicle>();
 Thread.sleep(10000); // wait for profiler to be configured
 long startTime = System.nanoTime();
 for (int i = 0; i < 100000; i++) {
// vehicleList.add(new Vehicle((short)4, "Audi", "A8", 3000, 100000,
// (short)5, FuelType.PETROL, "RR1111", "Red", TransmissionType.AUTO,
// VehicleType.FOUR_WHEELER, UsageType.PERSONAL));

vehicleList.add(Vehicle.builder().wheels((short)4).brand("Audi").model("A8").engineCapacity(3000)
 .price(100000).seatingCapacity((short)5).fuelType(FuelType.PETROL).regNo("RR1111")
 .color("Red").transmissionType(TransmissionType.AUTO).vehicleType(VehicleType.FOUR_WHEELER)
 .usageType(UsageType.PERSONAL).build());
 }
 double timeTaken = (System.nanoTime()-startTime)/1000.0;
 System.out.println("Time taken in microseconds: "+timeTaken);
 System.gc();
 Thread.sleep(5000);
 }

The initial and final sleep() calls give us time to initialize capturing and at the end to allow the system to settle down after the GC is invoked. The code creates 100000 objects using either of the approaches and measures the time spent in initializing so many objects.

As the performance was analyzed, below were the results. This definitely shows an improvement in performance time. But the value is just 20 milli seconds for creation of 100,000 objects.

Builder resource consumption:

Builder Resource Consumption

Constructor Resource Consumption:

Constructor Resource Consumption

Resource consumption almost remained the same in both the cases across all the iterations. Thus, builder probably going through optimizations by Java compiler is a sure winner which favors in a better quality of code, especially when more number of parameters are involved.