I have this simple class
public class ErrorDetails {
private String param = null;
private String moreInfo = null;
private String reason = null;
...
}
After refactoring, I added #Data and #Builder, but all the instantiations doesn't work any more
ErrorDetails errorDetails = new ErrorDetails();
'ErrorDetails(java.lang.String, java.lang.String, java.lang.String)'
is not public in
'com.nordea.openbanking.payments.common.ndf.client.model.error.ErrorDetails'.
Cannot be accessed from outside package
If I removed #Builder, then it will work fine,
Why I cannot use #Data and #Builder together?
Lombok's #Builder must have #AllArgsConstructor in order to work
Adding also #AllArgsConstructor should do
Under the hood it build all fields using constructor with all fields
applying #Builder to a class is as if you added #AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the #Builder annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself.
The full config should be :
#Data
#Builder(toBuilder = true)
#AllArgsConstructor
#NoArgsConstructor
class ErrorDetails {
private String param; // no need to initiate with null
private String moreInfo;
private String reason;
}
Related
I'm using Spring framework,
and I faced the inheritance problem when I write Controller logic.
First of all,
this is my Controller code snippet
#PostMapping("/pay/detail")
public ResponseEntity<PayDetail.Response> getPayDetail(
#Valid PayDetail.Request payDetail
) {
... some code
}
and PayDetail class looks like this
public class PayDetail {
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public static class Request extends CommReqForm {
#NotNull(message = "Not null please")
private String work_type;
}
}
and CommReqForm
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CommReqForm {
#NotEmpty(message = "servicecode not empty")
private String servicecode;
#NotEmpty(message = "reqtype not empty")
private String reqtype;
}
I wish that I can validate both of PayDetail.Request and CommReqForm classes but It makes validation just only PayDetail.Request class.
How can I solve this problem?
#Valid cannot validate super class. I want to make both of sub class and super class validation.
Let's consider the following Entity:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Audi extends Car {
private String name;
private String headquarter;
private BigDecimal revenue = BigDecimal.ZERO;
private BigDecimal totalAssets = BigDecimal.ZERO;
}
I want to have all BigDecimal variables with BigDecimal.ZERO as default value.
If I initialize it immediately after declaration,
I got this warning:
#Builder will ignore the initializing expression entirely. If you want
the initializing expression to serve as default, add #Builder.Default.
If it is not supposed to be settable during building, make the field
final.
For an Entity with 10/15 BigDecimal, it's not so beautiful to have this annotation on each field.
Any alternative?
As Michael pointed out, you don't have many other options. If you've added the #Builder annotation, you must use #Builder.Default for each field in order to utilize the fluent builder API.
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Audi extends Car {
private String name;
private String headquarter;
#Builder.Default
private BigDecimal revenue = BigDecimal.ZERO;
#Builder.Default
private BigDecimal totalAssets = BigDecimal.ZERO;
}
Usage:
Audi audi = Audi.builder()
.name("Audi A3")
.headquarter("Germany")
.build();
System.out.println(audi);
Output:
Audi(name=Audi A3, headquarter=Germany, revenue=0, totalAssets=0)
If builder functionality is not needed, objects can be instantiated in the traditional Java way using a constructor. However, in this case, the #Builder annotation should be removed and default values must be set directly in the constructor or during declaration.
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Audi extends Car {
#NonNull
private String name;
#NonNull
private String headquarter;
private BigDecimal revenue = BigDecimal.ZERO;
private BigDecimal totalAssets = BigDecimal.ZERO;
}
Usage:
final Audi audi = new Audi("Audi A3", "Germany");
System.out.println(audi);
Output:
Audi(name=Audi A3, headquarter=Germany, revenue=0, totalAssets=0)
I am currently trying to remove some boilerplate code with lombok but have some trouble.
I have an abstract class AbstractParent,
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final field1;
private final field2;
then I have a Child Class like this
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public abstract class Child extends AbstractParent {
And I also have some classes extending the Child class
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
public abstract class ExtendedChild extends Child {
private final field1;
private final field2;
Since lombok can't use super in a constructor, I tried the #SuperBuilder Annotation instead of defining the Constructors manually but can't get the Application to start. Am I missing something completely? Is this even possible with lombok and spring?
The Error is:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.fu.extendedChild required a bean of type 'com.fu.extendedChild$extendedChildBuilder' that could not be found.
Action:
Consider defining a bean of type 'com.fu.extendedChild$extendedChildBuilder' in your configuration.
I was able to reproduce your problem with this code
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final String field1;
private final String field2;
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
abstract class Child extends AbstractParent {
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
class ExtendedChild extends Child {
private final String field1;
private final String field2;
}
What the #SuperBuilder does here on class ExtendedChild is
protected ExtendedChild(ExtendedChildBuilder<?, ?> b) {
super(b);
this.field1 = b.field1;
this.field2 = b.field2;
}
So it says you need an ExtendedChildBuilder instance in order to build an ExtendedChild instance. In order words, you have to have a builder in your spring context to be able to create your object.
This is not a good idea since a builder is stateful and not thread-safe. Furthermore, the builder pattern is here to be able to provide values whenever you need them before constructing your object. Using a builder as a Spring bean denies that advantage.
If this is immutability you want to achieve, then using plain old constructors with the right parameters is way better (and when done right, this is not boilerplate code, this is good design).
Then, Spring injection will be a child's play.
Please do not trade complexity for the sake of writing less code :)
I was setting the value of recordId from the child classes using the default constructor and was not using lombok #Builder initially. Eventually i decided to use the Builder here, but the problem now is lombok Builder overrides my default constructor internally hence the value is never set.
How can I put any hook too make lombok #Builder use my default constructor?
Parent class:
#Getter
#Setter
public abstract class Record {
private String recordId;
}
Child class:
#Getter
#Setter
#Builder
#ToString
#AllArgsConstructor
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord() {
setRecordId(RECORD_ID); //value of recordId being set
}
}
Lombok's #Builder simply does not use the default constructor. It passes its values to an all-args constructor so that this constructor can fill the new instance with these values. #Builder does not use setters or direct access to the fields to do so. So your default constructor is simply ignored by #Builder.
What you can do is write your own all-args constructor. In it, you set your value for recordId and assign the rest of the fields from the parameters.
I think you should create a constructor in your base class:
#Getter
#Setter
public abstract class Record {
private String recordId;
public Record(String recordId) {
this.recordId = recordId;
}
}
Then use it in the constructor of the inherited class:
#Getter
#Setter
#Builder
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord(String street, String city) {
super(RECORD_ID);
this.street = street;
this.city = city;
}
}
P.S. If you want to use Lombok Builder with inheritance you can use this technique.
Given I have the POJO:
import lombok.Builder;
import lombok.Data;
#Data
#Builder
public class SomeResponse {
private String author;
private String authorTitle;
private String teaser;
private String text;
private Long lastModified;
private Long created;
private Integer rating;
private Optional<Markdown> markdown;
private Optional<Integer> wordCount;
}
When I try to use the POJO in such normal Jackson construction:
restTemplate.getForObject(urlTemplate, SomeResponse.class,
productId.toString(), siteId.toString());
I get an exception, because there are private ctor in the SomeResponse class due to Lombok #Builder annotation.
How can I make it works without deleting Lombok #Builder annotation?
Also add #AllArgsConstructor and #NoArgsConstructor, possible with the right access values. See the documentation for appropriate parameters.
Disclosure: I am a lombok developer.