89

Is there any way to automate writing Builder patterns in IntelliJ?

For example, given this simple class:

class Film {
   private String title;
   private int length;

   public void setTitle(String title) {
       this.title = title;
   }

   public String getTitle() {
       return this.title;
   }

   public void setLength(int length) {
       this.length = length;
   }

   public int getLength() {
       return this.length;
   }
}

is there a way that I could get the IDE to generate this, or similar:

public class FilmBuilder {

    Film film;

    public FilmBuilder() {
        film = new Film();
    }

    public FilmBuilder withTitle(String title) {
        film.setTitle(title);
        return this;
    }

    public FilmBuilder withLength(int length) {
        film.setLength(length);
        return this;
    }

    public Film build() {
        return film;
    }
}
Martyn
  • 16,432
  • 24
  • 71
  • 104

12 Answers12

116

Use the Replace Constructor with Builder refactoring.

To use this function, click on the constructor's signature in your code, then right click and select the "Refactor" menu, then click "Replace Constructor with Builder..." to bring up the dialog box to generate the code.

Jason D
  • 8,023
  • 10
  • 33
  • 39
CrazyCoder
  • 389,263
  • 172
  • 990
  • 904
  • 15
    Is there a way to change "setX()" to "withX()", Serge? – ae6rt Jul 18 '13 at 19:05
  • 6
    Or can you change setX() to just x()? – Kurru Mar 01 '14 at 22:29
  • Nice! Never new that existed. Thanks – vikingsteve Aug 22 '14 at 06:59
  • 18
    You can change the name of the setter methods by clicking on the gear in the upper right hand corner of the create builder window and selecting "Rename Setters Prefix". Then you can change it to "with" or just remove the prefix entirely. – Chris B Jun 14 '16 at 19:23
  • 4
    Is it possible to make IntelliJ create the Builder class as an inner class automatically? – kit Oct 06 '19 at 16:40
  • You can do it in two steps, generate the builder, and use the refactor/move action to make it as an inner class. @user:2596742 – AElMehdi Nov 09 '19 at 18:13
  • I had to right click + "Show Context Actions" to be presented with this menu in my version of IntelliJ. – Steve Dec 08 '22 at 20:56
27

I found the built-in builder pattern generation in IntelliJ to be a bit clunky for a few reasons:

  1. It needs to use an existing constructor as reference.
  2. It's not quickly accessible via the "Generate" menu (command+N on OS X).
  3. It only generates external Builder classes. As others have mentioned, it's very common to use static inner classes when implementing the builder pattern.

The InnerBuilder plugin addresses all of these shortcomings, and requires no setup or configuration. Here's a sample Builder generated by the plugin:

public class Person {
    private String firstName;
    private String lastName;
    private int age;

    private Person(Builder builder) {
        firstName = builder.firstName;
        lastName = builder.lastName;
        age = builder.age;
    }

    public static final class Builder {
        private String firstName;
        private String lastName;
        private int age;

        public Builder() {
        }

        public Builder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }
}
Mansoor Siddiqui
  • 20,853
  • 10
  • 48
  • 67
  • 3
    Please, see [my answer](http://stackoverflow.com/a/35144261/875485) for a built-in solution to all of your mentioned problems. – jFrenetic Feb 02 '16 at 02:58
  • 2
    You make some good points @jFrenetic, but I still ultimately find the built-in approach to be too cumbersome, even with those workarounds. With the plugin, it's literally just `alt+shift+B`, `enter`, and BAM, you have your builder. :D – Mansoor Siddiqui Jun 16 '16 at 18:27
  • 1
    OMG this is a dream come true. The built in builder didn't do it for me - I prefer to have the builder as an inner class of the PoJo. Thanks a lovely tip! – Somaiah Kumbera Jan 03 '18 at 07:29
16

Here's how to overcome the shortcomings mentioned by Mansoor Siddiqui:

1) It needs to use an existing constructor as reference.

Which is very easy to generate. Just hit Alt + Ins on Windows to invoke Generate menu and choose Constructor.

2) It's not quickly accessible via the "Generate" menu (command+N on OS X)

Just go to Settings -> Keymap, search for Builder and assign it a shortcut of your choice (if you use this functionality very often, which is rarely the case). You can assign Alt + B for example.

Another alternative is Ctrl + Shift + A (Find Action). Start typing Builder and you'll immediately get access to the command:

Find Action dialog

You could use this shortcut for quickly getting access to any IntelliJ IDEA feature (this helps a lot when you don't remember exactly what the command is called and where to find it).

3) It only generates external Builder classes. As others have mentioned, it's very common to use static inner classes when implementing the builder pattern.

I also prefer my builders as static inner classes. Unfortunately, there is no straightforward way to do it, but it's still feasible. You just have to define the nested inner class yourself (leave it empty), and when you invoke Replace Constructor with Builder dialog, choose Use existing option and select your inner class. Works like a charm! Although, it would've been easier to make this option configurable.

Community
  • 1
  • 1
jFrenetic
  • 5,384
  • 5
  • 42
  • 67
12

If you are wondering if this can be used to create a class with an inner builder class as described by Joshua Block - you just have to define an empty inner class first, then check "Use existing" and search for your newly created (inner class) and hit "Refactor".

PS! Cursor must reside inside constructor (pre-written) in order to use the "Replace Constructor with Builder" refactoring function.

PålOliver
  • 2,502
  • 1
  • 23
  • 27
  • It's interesting that these plugins forget an important part of the builder pattern: you should still define required params in the constructor. Otherwise, you've moved from statically catching missing param errors to only catching at runtime. – EJ Campbell Jun 11 '15 at 02:44
  • @EJCampbell That would defeat _one_ of the purposes of using a Builder, which is to make code more readable by providing a way to attach names to the parameters, even if they're required. This helps when a constructor takes a fairly large number of parameters. It wouldn't be necessary if Java allowed us to name actual parameters in the call, like C# and Swift do. – ajb Apr 07 '16 at 01:39
5

The IntelliJ way to to this is, IMHO, is convoluted. There are out there two plugins (I prefer this one: https://plugins.jetbrains.com/plugin/7354) that serve the purpose much much better.

For example, I prefer having the Builder class as an inner class of the PoJo. To achieve that with IntelliJ you need few extra strokes.

Another plus for the plugin is the location of the functionality (in the Generate... context menu).

nucatus
  • 2,196
  • 2
  • 21
  • 18
5

Lombok is the easiest way to do it! (It has a Intellij plugin for syntax support https://plugins.jetbrains.com/plugin/6317-lombok-plugin)

Just add a @Builder annotation, and behind the walls it will add a builder implementation inside the object.

With Lombok:

import lombok.Builder;
import lombok.Singular;
import java.util.Set;

@Builder
public class BuilderExample {
  @Builder.Default private long created = System.currentTimeMillis();
  private String name;
  private int age;
  @Singular private Set<String> occupations;
}

Without Lombok:

import java.util.Set;

public class BuilderExample {
  private long created;
  private String name;
  private int age;
  private Set<String> occupations;
  
  BuilderExample(String name, int age, Set<String> occupations) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  private static long $default$created() {
    return System.currentTimeMillis();
  }
  
  public static BuilderExampleBuilder builder() {
    return new BuilderExampleBuilder();
  }
  
  public static class BuilderExampleBuilder {
    private long created;
    private boolean created$set;
    private String name;
    private int age;
    private java.util.ArrayList<String> occupations;
    
    BuilderExampleBuilder() {
    }
    
    public BuilderExampleBuilder created(long created) {
      this.created = created;
      this.created$set = true;
      return this;
    }
    
    public BuilderExampleBuilder name(String name) {
      this.name = name;
      return this;
    }
    
    public BuilderExampleBuilder age(int age) {
      this.age = age;
      return this;
    }
    
    public BuilderExampleBuilder occupation(String occupation) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }

      this.occupations.addAll(occupations);
      return this;
    }
    
    public BuilderExampleBuilder clearOccupations() {
      if (this.occupations != null) {
        this.occupations.clear();
      }
      
      return this;
    }

    public BuilderExample build() {
      // complicated switch statement to produce a compact properly sized immutable set omitted.
      Set<String> occupations = ...;
      return new BuilderExample(created$set ? created : BuilderExample.$default$created(), name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      return "BuilderExample.BuilderExampleBuilder(created = " + this.created + ", name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
    }
  }
}

Source: https://projectlombok.org/features/Builder

veysiertekin
  • 1,731
  • 2
  • 15
  • 40
  • 1
    lombok is code generation with extra steps. because of this is confuses a number of static analysis tools because the final code they read doesn't look like java. – ThisCompSciGuy Sep 04 '20 at 01:28
2

In my opinion, the simplest way to get the inner static class instead of a separate are:

  1. If you don't have a constructor yet, generate one using Generate a constructor dialog
  2. Then, use Replace Constructor with Builder
  3. Move the newly created class back to the initial one using Move refactoring (Hotkey: F6)
Gaket
  • 6,533
  • 2
  • 37
  • 67
  • 1
    This plugin seems to achieve exactly this in just one step: https://plugins.jetbrains.com/plugin/7354-innerbuilder – jivimberg Jun 05 '18 at 16:39
0

As a complement to @CrazyCoder's answer, I think it's very useful to know that in the upper-right side of Replace Constructor with Builder dialog there is a configuration button that you can use to rename setters' prefix.

tibtof
  • 7,857
  • 1
  • 32
  • 49
0

For those wanting to replace "too many parameters" with a builder pattern, do "extract parameter object" and then the convert constructor to builder mentioned in other answers here.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
0

Generate constructor of the class by right clicking on class Generate>Constructor. Then, on Windows, press is Ctrl+Shift+A to find action and type "builder", select "Replace constructor with builder.."

Vijay Nandwana
  • 2,476
  • 4
  • 25
  • 42
0

I have found the Effective Inner Builder to be the best option as it adds null checks and can start with just the members defined.

ThisCompSciGuy
  • 176
  • 2
  • 11
0

In addition to CrazyCoder's answer and Chris B's comment . There's a huge advantage using the refactor of Intellij compared to the usage of InnerBuilder plugin mentioned by Mansoor Siddiqui, that if the constructor is already used in code it will be refactored to the new created builder which is not the case with InnerBuilder.

AryehSa
  • 11
  • 2