Lombok #Builder with Java 8 Lambda Builder Pattern - java

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.

Related

How to generate getters for lombok Builder?

I have the following definition:
#Value
#Builder(toBuilder = true)
public class MyEntity {
String name;
}
When trying to use a getter on a builder, e.g. MyEntityBuilder.getName(), IDEA states that it "Cannot resolve method". Also, IDEA doesn't auto-complete it.
The only available method (except build()) is the setter: MyEntityBuilder name(String name);
Is there a way to generate getter on lombok generated builders? Thanks.
Using Lombok 1.8.16.
There seems to be no option to do it out of the box according to the Lombok docs or the source code.
If you want to inspect the contents of the builder, the best you can do, it seems, is to build the object and inspect the field from there.
You mentioned you were using IntelliJ IDEA as your IDE. It so happens that there is an IDEA plugin exactly for this:
https://plugins.jetbrains.com/plugin/6317-lombok
Once the plugin is installed, IDEA should recognize Lombok's annotation syntax.
Builders do not,
generally,
have getters.
The builder pattern is this:
Create the builder.
Set values in the builder.
Call build().
As answer from DwB suggests builder pattern does not need nor use getters.
You can have a getter for Lombok builder values but I think that it would not be very useful. You can customize builder this way:
#Value
#Builder(toBuilder = true)
public class MyEntity {
String name;
String name2; // just something to set also
// Customized builder
public static class MyEntityBuilder {
private String name;
public String getName() {
return this.name;
}
// This is not actually needed but just as an example howto
// customise a setter.
public MyEntityBuilder name(String name) {
this.name = name;
return this;
}
}
}
And to test it (Junit5):
#Test
void test() {
MyEntityBuilder meb = MyEntity.builder();
var myEntity = meb
// You need to set this first to access it later
.name("Name #1")
// The benefit having a getter ?
.name2(meb.getName())
.build();
assertEquals(myEntity.getName(), myEntity.getName2());
}

How can I use Java Enums with Amazon DynamoDB and AWS SDK v2?

I am trying to implement a simple java event-handler lambda for AWS. It receives sqs events and should make appropriate updates to the dynamoDB table.
One of the attributes in this table is a status field that has 4 defined states; therefore I wanted to use an enum class in java and map it to this attribute.
Under AWS SDK v1 I could use the #DynamoDBTypeConvertedEnum annotation. But it does not exist anymore in v2. Instead, there is the #DynamoDbConvertedBy() which receives a converter class reference. There is also an EnumAttributeConverter class which should work nicely with it.
But for some reason, it does not work. The following is a snip from my current code:
#Data
#DynamoDbBean
#NoArgsConstructor
public class Task{
#Getter(onMethod_ = {#DynamoDbPartitionKey})
String id;
...
#Getter(onMethod_ = {#DynamoDbConvertedBy(EnumAttributeConverter.class)})
ExportTaskStatus status;
}
The enum looks as follows:
#RequiredArgsConstructor
public enum TaskStatus {
#JsonProperty("running") PROCESSING(1),
#JsonProperty("succeeded") COMPLETED(2),
#JsonProperty("cancelled") CANCELED(3),
#JsonProperty("failed") FAILED(4);
private final int order;
}
With this, I get the following exception when launching the application:
Class 'class software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter' appears to have no default constructor thus cannot be used with the BeanTableSchema
For anyone else coming here, it looks do me like just dropping the annotation from the enum altogether works just fine, i.e. the SDK applies the provided attribute converters implicitly. This is also mentioned in this Github issue. My own class looks like this (Brand is an enum here), and the enum is converted without any issues when fetching items.
#Value
#Builder(toBuilder = true)
#DynamoDbImmutable(builder = User.UserBuilder.class)
public class User {
#Getter(onMethod = #__({#DynamoDbPartitionKey}))
String id;
Brand brand;
...
}
How can I use Java Enums with Amazon DynamoDB and AWS SDK v2?
Although the documentation doesn't state it, the DynamoDbConvertedBy annotation requires any AttriuteConverter you supply to contain a parameterles default constructor
Unfortunately for you and me, whoever wrote many of the built-in AttributeConverter classes decided to use static create() methods to instantiate them instead of a constructor (maybe they're singletons under the covers? I don't know). This means anyone who wants to use these helpful constructor-less classes like InstantAsStringAttributeConverter and EnumAttributeConverter needs to wrap them in custom wrapper classes that simple parrot the converters we instantiated using create. For a non-generic typed class like InstantAsStringAttributeConverter, this is easy. Just create an wrapper class that parrots the instance you new up with create() and refer to that instead:
public class InstantAsStringAttributeConverterWithConstructor implements AttributeConverter<Instant> {
private final static InstantAsStringAttributeConverter CONVERTER = InstantAsStringAttributeConverter.create();
#Override
public AttributeValue transformFrom(Instant instant) {
return CONVERTER.transformFrom(instant);
}
#Override
public Instant transformTo(AttributeValue attributeValue) {
return CONVERTER.transformTo(attributeValue);
}
#Override
public EnhancedType<Instant> type() {
return CONVERTER.type();
}
#Override
public AttributeValueType attributeValueType() {
return CONVERTER.attributeValueType();
}
}
Then you update your annotation to point to that class intead of the actual underlying library class.
But wait, EnumAttributeConverter is a generic typed class, which means you need to go one step further. First, you need to create a version of the converter that wraps the official version but relies on a constructor taking in the type instead of static instantiation:
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
public class EnumAttributeConverterWithConstructor<T extends Enum<T>> implements AttributeConverter<T> {
private final EnumAttributeConverter<T> converter;
public CustomEnumAttributeConverter(final Class<T> enumClass) {
this.converter = EnumAttributeConverter.create(enumClass);
}
#Override
public AttributeValue transformFrom(T t) {
return this.converter.transformFrom(t);
}
#Override
public T transformTo(AttributeValue attributeValue) {
return this.converter.transformTo(attributeValue);
}
#Override
public EnhancedType<T> type() {
return this.converter.type();
}
#Override
public AttributeValueType attributeValueType() {
return this.converter.attributeValueType();
}
}
But that only gets us half-way there-- now we need to generate a version for each enum type we want to convert that subclasses our custom class:
public class ExportTaskStatusAttributeConverter extends EnumAttributeConverterWithConstructor<ExportTaskStatus> {
public ExportTaskStatusAttributeConverter() {
super(ExportTaskStatus.class);
}
}
#DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)
public ExportTaskStatus getStatus() { return this.status; }
Or the Lombok-y way:
#Getter(onMethod_ = {#DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)})
ExportTaskStatus status;
It's a pain. It's a pain that could be solved with a little bit of tweaking and a tiny bit of reflection in the AWS SDK, but it's where we're at right now.
I am thinking that your annotations might actually be the problem here. I would remove all annotations that mention a constructor, and instead, write out your own constructor(s). For both Task and TaskStatus.
The dynamodb-enhanced SDK does this out of the box.
When you declare a #DynamoDbBean the DefaultAttributeConverterProvider provides a long list of possible ways to convert attributes between java types, including an EnumAttributeConverter which is used if type.rawClass().isEnum() is true. So you don't need to worry about it.
If you ever wanted to extend the number of converters, you would need to add the converterProviders annotation parameter, and declare the default one (or omit it), as well as any other providers you want.
Example:
#DynamoDbBean(converterProviders = { DefaultAttributeConverterProvider.class, MyCustomAttributeConverterProvider.class });
Solution based on watkinsmatthewp Answer:
public class TaskStatusConverter implements AttributeConverter<TaskStatus> {
#Delegate
private final EnumAttributeConverter<TaskStatus> converter;
public TaskStatusConverter() {
converter = EnumAttributeConverter.create(TaskStatus.class);
}
}
Task status attribute looks like this:
#Getter(onMethod_ = {#DynamoDbConvertedBy(TaskStatusConverter.class)})
TaskStatus status;

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 make Lombok's builder public?

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();

Java BuilderTestPattern - how to avoid boilerplate?

I have a lot of value objects in my project.
I'm using project lombok to eliminate some boilerplate, so my value objects look like the following one:
#Value
#Accessors(fluent = true)
public class ValueObject {
private final String firstProp;
private final int secondProp;
}
Not bad, almost no boilerplate.
And now, I'm using the all-args constructor quite often in my tests. It looks quite messy, so I thought I will introduce Builder Pattern variant instead:
public class ValueObjectBuilder {
private static final int DEFAULT_VALUE_FOR_SECOND_PROP = 666;
private String firstProp = "default value for first prop;
private int secondProp = DEFAULT_VALUE_FOR_SECOND_PROP;
private ValueObjectBuilder() {}
public static ValueObjectBuilder newValueObject() {
return new ValueObjectBuilder();
}
public ValueObjectBuilder withFirstProp(String firstProp) {
this.firstProp = firstProp
return this;
}
public ValueObjectBuilder withFirstProp(int secondProp) {
this.secondProp = secondProp;
return this;
}
public ValueObject build() {
return new ValueObject(
firstProp, secondProp
);
}
}
and the code looks quite nice now:
ValueObjectBuilder
.newValueObject()
.withFirstProp("prop")
.withSecondProp(15)
.build();
Now, the problem is - as I mentioned, I have to write a lot of similar classes... I'm already tired with copy-paste'ing them.
What I'm looking for, is a black-magic-smart-tool, which will somehow generate this code for me.
I know, there is a #Builder annotation in Lombok, but it doesn't meet my requirements. Here's why:
1) I'm unable to provide default values in lombok's Builder. Well, actually, it is possible - by implementing builder class template myself like
#Builder
public class Foo {
private String prop;
public static class FooBuilder() {
private String prop = "def value";
...
}
}
which generates some boilerplate too.
2) I can't find any way to put prefix on each field accessor in lombok's builder. Maybe #Wither could help here? But I don't know, how to use it properly.
3) The most important reason: I'm not creating a "natural" builder. As far as I understand, lombok is designed to create Builder for a given, annotated class - I don't know if there is a way to return any other object from within build() method.
So, to sum up:
Do you know any tool which could possibly help me? Or maybe all those things I mentioned are in fact possible to achieve using Lombok?
EDIT
Ok, so I probably found a solution to this particular case. With lombok we can use:
#Setter
#Accessors(chain = true, fluent = true)
#NoArgsConstructor(staticName = "newValueObject")
public class ValueObjectBuilder {
private String firstProp = "default";
private int secondProp = 666;
public ValueObject build() {
return new ValueObject(firstProp, secondProp);
}
}
Cheers,
Slawek
I know this is old but if anyone else runs into this, I found an alternative solution for providing default values for a builder.
Override the builder method and provide the default values before returning a builder. So in the above case:
#Builder
public class Foo {
private String prop;
public static FooBuilder builder() {
return new FooBuilder().prop("def value");
}
}
It's not an ideal solution but beats having to override the whole builder itself or have a custom constructor (which is painful IMHO if there are a lot of variables. It would still be nice to have something along the lines of a #With or #Default annotation to handle this.
Try Bob-the-builder for eclipse. Hmm.. I guess that works best if you happen to be using eclipse! If you are not using eclipse, there are a few related projects mentioned at the bottom of the page linked here that may be useful.

Categories