121

I have a class called as "XYZClientWrapper" , which have following structure:

@Builder
XYZClientWrapper{
    String name;
    String domain;
    XYZClient client;
}

What I want no build function generated for property XYZClient client

Does Lombok supports such use case?

CuriousSuperhero
  • 6,531
  • 4
  • 27
  • 50
Vivek Goel
  • 22,942
  • 29
  • 114
  • 186

12 Answers12

211

Yes, you can place @Builder on a constructor or static (factory) method, containing just the fields you want.

Disclosure: I am a Lombok developer.

Roel Spilker
  • 32,258
  • 10
  • 68
  • 58
  • 1
    Yes, it could have any visibility, including `private`. The disclosure is to indicate that the answer or opinion might be biased. In this case it's not that relevant, but I took up the habit to add that to all my lombok related answers. – Roel Spilker Nov 27 '15 at 20:09
  • 79
    I stumbled over this question and answer since I have the same question in mind. The constructor solution is not really a good one in my case. I have a class with 29 properties (an entity) and only want one of them to not be included in the builder (in fact: the id). Is there no way of exclude mechanism just like in the @ToString? Main reason to use Lombok is to avoid clutter. A Constructor with 28 parameters is not what I want to see. – Staratnight Apr 12 '16 at 07:49
  • Did lombok project address the issue presented here by @Staratnight? – Stephan Oct 07 '16 at 14:54
  • 4
    @Staratnight This answer may help you: http://stackoverflow.com/a/39920328/363573 – Stephan Oct 07 '16 at 15:01
  • 53
    Is there any plan to add something like _ignore_ attribute to `@Builder` annotation or annotation `@Builder.Ignore` to fields ? – pirho Oct 12 '18 at 09:15
  • @Roel Spilker: I tried to set it on the "required-args" constructor generated by lombok (1.18.6), but this causes a NPE at the compiler (java8) :) `@RequiredArgsConstructor(onConstructor = @__({ @Builder }))` – rudi May 17 '19 at 15:39
  • 1
    @Staratnight Btw I think the constructor with 29 properties is a reason to think about refactoring) – chill appreciator Dec 04 '19 at 01:58
  • I agree with you in general. Since we use Lombok to help us with entities, 29 is reached easily on central Objects. Even if you try to get all in derived tables, you need to manage foreign keys. – Staratnight Dec 05 '19 at 05:57
  • 10
    Doesn't work if the fields have been initialized to default values. You can't reassign final fields. IMO, providing an `ignore` attribute on `Builder` is simply common sense. – Abhijit Sarkar Jan 16 '20 at 21:49
  • The second answer, using `final`, works perfect for me: https://stackoverflow.com/a/39920328/6555159 – socona Feb 11 '21 at 14:04
  • 4
    As you made clear that you are a Lombock developer, I am confused by the fact that you encourage to create a constructor, which is exactly what the builder tries to avoid. I don't know if you've read @pirho 's question, but it seems the next natural step on the library and it would be really helpful. – Carlos López Marí Mar 10 '21 at 19:55
  • How can we exclude a field of an object if it is an inner object otherwise we have to include it? – anas p a Mar 23 '22 at 09:40
  • 5
    Funny. Lombok provides ways how to avoid boilerplate things. Now it suggests what it once let us avoid. – Jin Kwon Apr 21 '22 at 02:18
  • 1
    Thanks for your work on Lombuk, this library has saved me some time. I wish I knew sooner I could annotate a constructor to customize the builder and not just the class. I think the docs could be improved to communicate common use cases like this and include examples, otherwise an amazing library. – Tina Nov 08 '22 at 20:13
  • There was an issue created to improve this, but it was closed: https://github.com/projectlombok/lombok/issues/2307 – gsgx Nov 12 '22 at 03:46
  • Note: the `@Builder` annotation has to be put on the constructor _instead_ of on the class. If it's on both locations, the one on the class appears to win in terms of which methods are added to the builder. – M. Justin Jun 27 '23 at 05:41
42

Alternatively, I found out that marking a field as final, static or static final instructs @Builder to ignore this field.

@Builder
public class MyClass {
   private String myField;

   private final String excludeThisField = "bar";
}

Lombok 1.16.10

Stephan
  • 41,764
  • 65
  • 238
  • 329
  • 2
    Thanks Stephan for the answer. In my case it does not work since the field I want to ignore is derived from parent class. – Staratnight Oct 09 '16 at 07:24
  • 5
    What?! I use builder on daily basis with final fields, how could they be ignored? :D currently using version 1.16.12 – Dominik Jan 17 '17 at 10:20
  • 2
    @Stephan How could this be true? I mean: the purpose of the builder pattern is to avoid a constructors with all these lengthy parameter lists. Field being final has nothing to do: builder's purpose is to gather all parameters in "chaining method fashion" instead of one huge bump. Besides, I checked that and adding final doesn't affect the builder's construction. – Piohen Feb 14 '17 at 10:05
  • What if I don't want them to be final and I don't want them to be on the builder? – Marinos An Feb 14 '19 at 13:29
  • 8
    Perfect! `@Builder` "adds" only final fields without values: `final String myField` - present, `final String myField=""` - ignored. :) – Cherry Feb 27 '19 at 14:37
  • 2
    Remember not everything is thread-safe, so making it `public static` isn't always the best idea. Besides, initializing the property like this only works for trivial cases, not when it needs to consider the values of other fields. – Abhijit Sarkar Jan 16 '20 at 21:38
  • @AbhijitSarkar, even in a single-threaded context public static fields are very problematic (unless they are immutable). I don't see how public static fields are related with thread-safety. – yaccob May 07 '21 at 16:35
  • @Stephan That is because they haven'tbeen initialized. This is why he sets it to "bar" in example. Same result with `@Generated`. The generator gives it a value & makes it immutable. – Nate T May 31 '21 at 15:04
  • Exactly as @Cherry said the fields are "ignored" by being initialised with nulls. – Ola May 07 '23 at 15:39
28

Create the builder in code and add a private setter for your property.

@Builder
XYZClientWrapper{
    String name;
    String domain;
    XYZClient client;

    public static class XYZClientWrapperBuilder {
        private XYZClientWrapperBuilder client(XYZClient client) { return this; }
    }
}
Bill H
  • 470
  • 1
  • 6
  • 12
15

Here is my preferred solution. With that, you can create your field client at the end and have it depending on other fields that previously set by the builder.

XYZClientWrapper{
    String name;
    String domain;
    XYZClient client;
    
    @Builder
    public XYZClientWrapper(String name, String domain) {
        this.name = name;
        this.domain = domain;
        this.client = calculateClient();
    }
}
Corentin
  • 304
  • 6
  • 16
SexyNerd
  • 1,084
  • 2
  • 12
  • 21
9

For factory static method example

class Car {
   private String name;
   private String model;


   private Engine engine; // we want to ignore setting this
   
   @Builder
   private static Car of(String name, String model){
      Car car=new Car();
      car.name = name;
      car.model = model;
      constructEngine(car); // some static private method to construct engine internally
      return car;  
   }

   private static void constructEngine(Car car) {
       // car.engine = blabla...
       // construct engine internally
   }
}

then you can use as follows:

Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available

Notice the static method of will be called whenever build() is called, so doing something like Car.builder().name("Toyota") won't actually set the value "Toyota" into name unless build() is called and then assigning logic within the constructor static method of is executed.

Also, Notice that the of method is privately accessed so that build method is the only method visible to the callers

Youans
  • 4,801
  • 1
  • 31
  • 57
5

I found that I was able to implement a "shell" of the static Builder class, add the method I want to hide with a private access modifier, and it is no longer accessible in the builder. Likewise I can add custom methods to the builder as well.

package com.something;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;

@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MyClass{

    //The builder will generate a method for this property for us.
    private String anotherProperty;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "localDateTime", column = @Column(name = "some_date_local_date_time")),
            @AttributeOverride(name = "zoneId", column = @Column(name = "some__date_zone_id"))
    })
    @Getter(AccessLevel.PRIVATE)
    @Setter(AccessLevel.PRIVATE)
    private ZonedDateTimeEmbeddable someDateInternal;

    public ZonedDateTime getSomeDate() {
        return someDateInternal.toZonedDateTime();
    }

    public void setSomeDate(ZonedDateTime someDate) {
        someDateInternal = new ZonedDateTimeEmbeddable(someDate);
    }

    public static class MyClassBuilder {
        //Prevent direct access to the internal private field by pre-creating builder method with private access.
        private MyClassBuilder shipmentDateInternal(ZonedDateTimeEmbeddable zonedDateTimeEmbeddable) {
            return this;
        }

        //Add a builder method because we don't have a field for this Type
        public MyClassBuilder someDate(ZonedDateTime someDate) {
            someDateInternal = new ZonedDateTimeEmbeddable(someDate);
            return this;
        }
    }

}
Richard Collette
  • 5,462
  • 4
  • 53
  • 79
5

Adding a so called 'partial builder' to the class with Lombok @Builder can help. The trick is to add a inner partial builder class like this:

@Getter
@Builder
class Human {
    private final String name;
    private final String surname;
    private final Gender gender;
    private final String prefix; // Should be hidden, depends on gender

    // Partial builder to manage dependent fields, and hidden fields
    public static class HumanBuilder {

        public HumanBuilder gender(final Gender gender) {
            this.gender = gender;
            if (Gender.MALE == gender) {
                this.prefix = "Mr.";
            } else if (Gender.FEMALE == gender) {
                this.prefix = "Ms.";
            } else {
                this.prefix = "";
            }
            return this;
        }

        // This method hides the field from external set 
        private HumanBuilder prefix(final String prefix) {
            return this;
        }

    }

}

PS: @Builder allows the generated builder class name to be changed. The example above assumed the default builder class name is used.

Dogan Ersoz
  • 81
  • 1
  • 6
4

I found one more solution You can wrap your field into initiated final wrapper or proxy. The easiest way to wrap it into AtomicReference.

@Builder
public class Example {
    private String field1;
    private String field2;
    private final AtomicReference<String> excluded = new AtomicReference<>(null);
}

You can interact with it inside by get and set methods but it won't be appeared in builder.

excluded.set("Some value");
excluded.get();
kattoha
  • 171
  • 5
0

I have another approach using @Delegate and Inner Class, which supports "computed values" for the excluded fields.

First, we move the fields to be excluded into an Inner Class to avoid Lombok from including them in the Builder.

Then, we use @Delegate to expose Getters/Setters of the builder-excluded fields.

Example:

@Builder
@Getter @Setter @ToString
class Person {

    private String name;
    private int value;
    /* ... More builder-included fields here */

    @Getter @Setter @ToString
    private class BuilderIgnored {

        private String position; // Not included in the Builder, and remain `null` until p.setPosition(...)
        private String nickname; // Lazy initialized as `name+value`, but we can use setter to set a new value
        /* ... More ignored fields here! ... */

        public String getNickname(){ // Computed value for `nickname`
            if(nickname == null){
                nickname = name+value;
            }
            return nickname;
        }
        /* ... More computed fields' getters here! ... */

    }
    @Delegate @Getter(AccessLevel.NONE) // Delegate Lombok Getters/Setters and custom Getters
    private final BuilderIgnored ignored = new BuilderIgnored();

}

It will be transparent to outside of this Person class that position and nickname are actually inner class' fields.

Person p = Person.builder().name("Test").value(123).build();
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=null, nickname=Test123))
p.setNickname("Hello World");
p.setPosition("Manager");
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=Manager, nickname=Hello World))

Pros:

  • Do not force the excluded fields to be final
  • Support computed values for the excluded fields
  • Allow computed fields to refer to any fields set by the builder (In other words, allow the inner class to be non-static class)
  • Do not need to repeat the list of all fields (Eg. listing all fields except the excluded ones in a constructor)
  • Do not override Lombok library's @Builder (Eg. creating MyBuilder extends FooBuilder)

Cons:

  • The excluded fields are actually fields of Inner Class; however, using private identifier with proper Getters/Setters you can mimic as if they were real fields
  • Therefore, this approach limits you to access the excluded fields using Getters/Setters
  • The computed values are lazy initialized when Getters are invoked, not when .build().
Northnroro
  • 525
  • 1
  • 6
  • 10
0

One method I like and use is this. Keep required parameters in constructor, and set optional through builder. Works if number of required is not very big.

class A {
    private int required1;
    private int required2;
    private int optional1;
    private int optional2;

    public A(int required1, int required2) {
        this.required1 = required1;
        this.required2 = required2;
    }

    @Builder(toBuilder = true)
    public A setOptionals(int optional1, int optional2) {
        this.optional1 = optional1;
        this.optional2 = optional2;
        return this;
    }
}

And then construct it with

A a = new A(1, 2).builder().optional1(3).optional2(4).build();

Nice thing with this approach is that optionals can also have default value.

Vuk Djapic
  • 816
  • 13
  • 29
-1

One approach I have used before was to group instance fields into Configuration fields and Session fields. Configuration fields go as class instances and are visible to the Builder, while Session fields go into a nested private static class and are accessed via a concrete final instance field (which the Builder will ignore by default).

Something like this:

@Builder
class XYZClientWrapper{
    private String name;
    private String domain;
 
    private static class Session {
        XYZClient client;
    }

    private final Session session = new Session();

    private void initSession() {
        session.client = ...;
    }
 
    public void foo() {
        System.out.println("name: " + name);
        System.out.println("domain: " + domain;
        System.out.println("client: " + session.client);
    }
}

MEE
  • 2,114
  • 17
  • 21
-2

To exclude field from builder, try using @Builder.Default

ClydeFrog
  • 912
  • 1
  • 14
  • 38
Gogogo
  • 1
  • 1
  • 1
    That just sets a default value for the field if that field isn't specified as part of the building. – b15 Feb 21 '22 at 18:56