Is it possible to make Lombok's builder public? - java

I am using Lombok library in my project and I am not able to use a class annotated with #Builder in outer packages.
Is there a way to make the builder public?
MyClass instance = new MyClass.MyClassBuilder().build();
The error is:
'MyClassBuilder()' is not public in
'com.foo.MyClass.MyClassBuilder'. Cannot be accessed
from outside package

#Builder already produces public methods, it's just the constructor that's package-private. The reason is that they intend for you to use the static builder() method, which is public, instead of using the constructor directly:
Foo foo = Foo.builder()
.property("hello, world")
.build();
If you really, really, really want the constructor to be public (there seems to be some suggestion that other reflection-based libraries might require it), then Lombok will never override anything that you've already declared explicitly, so you can declare a skeleton like this with a public constructor and Lombok will fill in the rest, without changing the constructor to package-private or anything.
#Builder
public class Foo
{
private final String property;
public static class FooBuilder
{
public FooBuilder() { }
// Lombok will fill in the fields and methods
}
}
This general strategy of allowing partial implementations to override default behaviour applies to most (maybe all) other Lombok annotations too. If your class is annotated with #ToString but you already declared a toString method, it will leave yours alone.
Just to show you everything that gets generated, I wrote the following class:
#Builder
public class Foo
{
private final String property;
}
I then ran it through delombok to see everything that was generated. As you can see, everything is public:
public class Foo
{
private final String property;
#java.beans.ConstructorProperties({"property"})
Foo(final String property) {
this.property = property;
}
public static FooBuilder builder() {
return new FooBuilder();
}
public static class FooBuilder
{
private String property;
FooBuilder() { }
public FooBuilder property(final String property) {
this.property = property;
return this;
}
public Foo build() {
return new Foo(property);
}
public String toString() {
return "Foo.FooBuilder(property=" + this.property + ")";
}
}
}

The problem is you are using #Builder in the wrong way.
When Builder Pattern is used, you only need to use the static method to invoke it and then build, for example:
MyClass instance = MyClass.builder().build(); .
Please do not new the MyClassBuilder again, it breaks the encapsulation the pattern has since you are directly using the inner MyClassBuilder class. This constructor is been hided from outside, that's why you get the not accessible error. Instead it provides you the static method builder().

I have found this neat workaround:
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
#Builder
public class Customer {
private String id;
private String name;
public static MessageBuilder builder() {return new CustomerBuilder();}
}

The problem with this builder annotation is that, if you delombok you'll see, the generated constructor for the builder has no access indicator (public, private, protected) therefore is only visible within the same package.
This would work if the extended classes were in the same package.
I'm having the same problem and I think that lombok does not support this, for now.
I was able to find the feature request in here https://github.com/rzwitserloot/lombok/issues/1489
My suggestion is to hard implement builder pattern in this class.

as mentioned you can use the builder, now instead of user property builder() will return the instance create so you can treat as normal builder ( no need to use property)
instance = MyClass.MyClassBuilder().property1(value1).property1(value2).build();

Related

Use custom setter in Lombok's builder with superclass

I want to use custom setter in Lombok's builder and overwrite 1 method, like this
#SuperBuilder
public class User implements Employee {
private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();
private String username;
private String password;
public static class UserBuilder {
public UserBuilder password(String password) {
this.password = ENCODER.encode(password);
return this;
}
}
}
but I have this compilation error
Existing Builder must be an abstract static inner class.
In contrast to #Builder, #SuperBuilder generates two builder classes, a public and a private one. Both are heavily loaded with generics to ensure correct type inference.
If you want to add or modify a method to the builder class, you should have a look at the uncustomized delomboked code and copy&paste the public abstract static class header from there. Otherwise you'll likely get the generics wrong, leading to compiler errors you won't be able to fix. Also have a look at the return types and statements of the generated methods to make sure you define that correctly.
The #SuperBuilder documentation also mentions this:
Due to the heavy generics usage, we strongly advice to copy the builder class definition header from the uncustomized delomboked code.
In your case, you have to customize the builder as follows:
public static abstract class UserBuilder<C extends User, B extends User.UserBuilder<C, B>> {
public B password(final int password) {
this.password = ENCODER.encode(password);
return self();
}
}

Error while use builder(lombok) in constructor annotation

#Data
#Builder
public static class Common {
private String common1;
private String common2;
}
#Getter
public static class Special extends Common {
private String special1;
#Builder
public Special(String common1, String common2, String special1) {
super(common1, common2);
this.special1 = special1;
}
}
The below error occurs :
Error:(149, 9) java: builder() in com.example.home.ExampleDTO.Special cannot override builder() in com.example.home.ExampleDTO.Common
return type com.example.home.ExampleDTO.Special.SpecialBuilder is not compatible with com.example.home.ExampleDTO.Common.CommonBuilder
And when I put (builderMethodName = "b") this parameter in #Builder(Special constructor) then it works fine.
#Builder(builderMethodName = "b")
public Special(String common1, String common2, String special1) {
I have no idea, why the first code gives error.
Please help me out.
Thank you
#Builder creates a static method builder() in both classes; it returns an instance of the respective builder. But the return types of the methods are not compatible, because SpecialBuilder and CommonBuilder are different and unrelated classes: #Builder does not (and can not technically) consider the inheritance relation between the classes. So the compiler complains about two methods with the same name, no arguments, but different return types. This is not possible in Java.
To solve this you have two choices:
Use #SuperBuilder on both classes. #SuperBuilder is designed to work with inheritance.
As you already found out, you can rename the method in one of the classes.

Is it possible to use Lombok #Builder, starting from static method?

I want Lombok to take care of my builder API, while also having a constructor to start with.
I started out with a constructor on the #Data class combined with #Builder(toBuilder = true), but that left me with forcing invalid or dummy values on final fields as well as a less expressive fluent API. I finally solved my situation using a static method, but I'm hoping Lombok has a better solution for my use case.
API using toBuilder
fooHandler.accept(new TweakedFoo(Foo.class, Mode.QUICK).toBuilder()
.mappingOutcomeFor(FooOutcome.class)
.mappingOutcome(toEvent(BarOutcome.class))
.build()));
API using static method
fooHandler.accept(tweakFoo(Foo.class, Mode.QUICK)
.mappingOutcomeFor(FooOutcome.class)
.mappingOutcome(toEvent(BarOutcome.class))
.build()));
See how the second setup flows better?
Respective Lombok setup (simplified)
#Data
#Builder(toBuilder = true)
public class TweakedFoo {
private final Class<Foo> from;
private final Mode mode;
private final Class<?> to;
public TweakedFoo(Class<Foo> from, Mode mode) {
this.from = from;
this.mode = mode;
this.to = null; // eww
}
}
And:
#Data
#Builder(builderMethodName = "useTweakedFooDotTweakedFooInsteadPlease")
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TweakedFoo {
private final Class<Foo> from;
private final Mode mode;
private final Class<?> to;
public static TweakedFooBuilder tweakFoo(Class<Foo> from, Mode mode) {
return TweakedFoo.useTweakedFooDotTweakedFooInsteadPlease()
.from(from)
.mode(mode);
}
}
The actual parameters don't make much sense here, but this setup illustrates my real-world use case.
Not only is the second approach more concise, it needs no dummy constructor field initialization and also it hides the constructor so you can't get an instance other than through the builder. However, the second approach requires me to obscure the builder starting method Lombok generates in favor of my own static method.
Is there a better way with Lombok?
You can customize your builder() method by simply implementing it yourself:
#Data
#Builder
public class TweakedFoo {
// ...
public static TweakedFooBuilder builder(Class<Foo> from, Mode mode) {
return new TweakedFooBuilder()
.from(from)
.mode(mode);
}
// ...
}
Lombok will not generate another builder() method in this case, because it recognizes the existing method with the same name. If you want the method to be named differently, e.g. tweakFoo, use #Builder(builderMethodName="tweakFoo").

Lombok #Builder with Java 8 Lambda Builder Pattern

I am trying to use Lombok with the Java 8 Lambda builder pattern introduced here.
POJO:
#JsonInclude(Include.NON_NULL)
#Builder
#NoArgsConstructor
#RequiredArgsConstructor
#AllArgsConstructor
public class RestResponse<T> {
#Getter #Setter #Builder.Default private Boolean success = true;
#Getter #Setter #NonNull private T data;
public static class RestResponseBuilder<T> {
public RestResponseBuilder<T> with(Consumer<RestResponseBuilder<T>> builderFunction) {
builderFunction.accept(this);
return this;
}
public RestResponse<T> createRestResponse() {
return new RestResponse<T>(success, data);
}
}
}
Usage:
#GetMapping(value = "/testLambdaBuilder", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public RestResponse<String> testEndpointLambdaBuilder() {
return new RestResponseBuilder<String>().with($ -> $.data = "helloWorld").createRestResponse();
}
Lombok seems to create a package level constructor for the builder. Is there a way to change it to public? The error I'm getting is:
The constructor RestResponse.RestResponseBuilder() is not visible
Because the builder class already partially exists, Lombok will simply inject the correct fields into your RestResponseBuilder. From the documentation (emphasis mine):
Each listed generated element will be silently skipped if that element
already exists (disregarding parameter counts and looking only at
names). This includes the builder itself: If that class already
exists, lombok will simply start injecting fields and methods inside
this already existing class, unless of course the fields / methods to
be injected already exist.
So if you want the field to have public visibility in the builder class then you just need to declare it there and Lombok will respect it:
public static class RestResponseBuilder<T> {
public T data;
public RestResponseBuilder<T> with(Consumer<RestResponseBuilder<T>> builderFunction) {
builderFunction.accept(this);
return this;
}
public RestResponse<T> createRestResponse() {
return new RestResponse<T>(success, data);
}
}
All of this said, your with method seems quite strange. You can just do this:
return new RestResponse.RestResponseBuilder<String>().data("helloWorld").createRestResponse();
What's the point in using the lambda builder pattern in combination with Lombok?
As I understand it, using the lambda builder pattern saves you from adding the setter methods in your builder (and implementing it correctly) every time you add a new variable to your pojo / value object.
But if you already use Lombok to generate your builder, all of the builder code is already generated. So there is no need to write the methods yourself. Lombok also updates the builder code whenever you change your pojo.
So I would recommend: either use Lombok and go with the default builder that gets generated OR write your builder yourself and use the lambda builder pattern to save you from writing and maintaining too many methods.

Jackson serialization: how to ignore superclass properties

I want to serialize a POJO class which is not under my control, but want to avoid serializing any of the properties which are coming from the superclass, and not from the final class. Example:
public class MyGeneratedRecord extends org.jooq.impl.UpdatableRecordImpl<...>,
example.generated.tables.interfaces.IMyGenerated {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
...
}
You can guess from the example that that this class is generated by JOOQ, and inherits from a complex base class UpdatableRecordImpl which also has some bean property-like methods, which cause problems during the serialization. Also, I have several similar classes, so it would be good to avoid duplicating the same solution for all of my generated POJOs.
I have found the following possible solutions so far:
ignore the specific fields coming from superclass using mixin technique like this: How can I tell jackson to ignore a property for which I don't have control over the source code?
The problem with this is that if the base class changes (e.g., a new getAnything() method appears in it), it can break my implementation.
implement a custom serializer and handle the issue there. This seems a bit overkill to me.
as incidentally I have an interface which describes exactly the properties I want to serialize, maybe I can mixin a #JsonSerialize(as=IMyGenerated.class) annotation...? Can I use this for my purpose?
But, from pure design point of view, the best would be to be able to tell jackson that I want to serialize only the final class' properties, and ignore all the inherited ones. Is there a way to do that?
Thanks in advance.
You can register a custom Jackson annotation intropector which would ignore all the properties that come from the certain super type. Here is an example:
public class JacksonIgnoreInherited {
public static class Base {
public final String field1;
public Base(final String field1) {
this.field1 = field1;
}
}
public static class Bean extends Base {
public final String field2;
public Bean(final String field1, final String field2) {
super(field1);
this.field2 = field2;
}
}
private static class IgnoreInheritedIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return m.getDeclaringClass() == Base.class || super.hasIgnoreMarker(m);
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new IgnoreInheritedIntrospector());
final Bean bean = new Bean("a", "b");
System.out.println(mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(bean));
}
}
Output:
{
"field2" : "b"
}
You can override the superclass' methods which you'd like to prevent from being output and annotate them with #JsonIgnore. The override shifts the control of property creation to the subclass while enabling its ability to filter it from the output.
For instance:
public class SomeClass {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
#Override
#JsonIgnore
public String superClassField1(...){
return super.superClassField1();
};
#Override
#JsonIgnore
public String superClassField2(...){
return super.superClassField2();
};
...
}
You can use this as well instead of unnecessary overrides
#JsonIgnoreProperties({ "aFieldFromSuperClass"})
public class Child extends Base {
private String id;
private String name;
private String category;
}
The good use of inheritance is that the child classes extend or add functionality. So the usual way is to serialize the data.
A workarround would be to use a Value Object (VO) or Data Transfer Object (DTO) with the fields you need to serialize. Steps:
Create a VO class with the fields that should be serialized.
Use BeanUtils.copyProperties(target VO, source data) to copy the properties
Serialize the VO instance.
Add the following annotation in your Base Class :
#JsonInclude(Include.NON_NULL)

Categories