How to Transform Any Type of Java Bean With BULL
BULL stands for Bean Utils Light Library, a transformer that recursively copies data from one object to another.
Join the DZone community and get the full member experience.
Join For FreeIntro
BULL (Bean Utils Light Library) is a Java-bean-to-Java-bean transformer that recursively copies data from one object to another. It is generic, flexible, reusable, configurable, and incredibly fast.
It's the only library able to transform Mutable, Immutable, and Mixed beans without any custom configuration.
This article explains how to use it, with a concrete example for each feature available.
1. Dependencies
<dependency>
<groupId>com.hotels.beans</groupId>
<artifactId>bull-bean-transformer</artifactId>
<version>2.0.1.1</version>
</dependency>
The project provides two different builds: one compatible with jdk 8
(or above) one with jdk 11
and since version 2.0.0 supports jdk 15
or above.
The latest version available of the library can be retrieved from the README file or from CHANGELOG (in case you need a jdk 8-
compatible version please refer to CHANGELOG-JDK8).
2. Features
The macro features explained in this article are:
- Bean transformation
- Bean validation
3. Bean Transformation
The bean transformation is performed by the Transformer
object, which can be obtained executing the following instruction:
BeanTransformer transformer = new BeanUtils().getTransformer();
Once we have a BeanTransformer
object instance, we can use the method transform
to get our object copied into another.
The method to use is: K transform(T sourceObj, Class<K> targetObject);
where the first parameter represents the source object and the second one is the destination class.
For example, given the source and destination class:
public class FromBean { public class ToBean {
private final String name; public BigInteger id;
private final BigInteger id; private final String name;
private final List<FromSubBean> subBeanList; private final List<String> list;
private List<String> list; private final List<ImmutableToSubFoo> nestedObjectList;
private final FromSubBean subObject; private ImmutableToSubFoo nestedObject;
// all args constructor // constructors
// getters and setters... // getters and setters
} }
The transformation can be obtained with the following line of code:
ToBean toBean = new BeanUtils().getTransformer().transform(fromBean, ToBean.class);
Note that the field order is not relevant.
Different Field Names Copy
Given two classes with the same number of fields but different names:
public class FromBean { public class ToBean {
private final String name; private final String differentName;
private final int id; private final int id;
private final List<FromSubBean> subBeanList; private final List<ToSubBean> subBeanList;
private final List<String> list; private final List<String> list;
private final FromSubBean subObject; private final ToSubBean subObject;
// all constructors // all args constructor
// getters... // getters...
} }
We need to define proper field mappings and pass them to theTransformer
object:
// the first parameter is the field name in the source object
// the second one is the the field name in the destination one
FieldMapping fieldMapping = new FieldMapping("name", "differentName");
Tansformer transformer = new BeanUtils().getTransformer().withFieldMapping(fieldMapping);
Then, we can perform the transformation:
ToBean toBean = transformer.transform(fromBean, ToBean.class);
Map Fields Between the Source and Destination Object
Case 1: A destination field value has to be retrieved from a nested class in the source object
Assuming that the object FromSubBean
is declared as follows:
public class FromSubBean {
private String serialNumber;
private Date creationDate;
// getters and setters...
}
and our source class and destination class are described as follow:
public class FromBean { public class ToBean {
private final int id; private final int id;
private final String name; private final String name;
private final FromSubBean subObject; private final String serialNumber;
private final Date creationDate;
// all args constructor // all args constructor
// getters... // getters...
} }
...and that the values for fields serialNumber
and creationDate
into the ToBean
object needs to be retrieved from subObject
, this can be done by defining the whole path to the property dot-separated:
FieldMapping serialNumberMapping = new FieldMapping("subObject.serialNumber", "serialNumber");
FieldMapping creationDateMapping = new FieldMapping("subObject.creationDate", "creationDate");
ToBean toBean = new BeanUtils().getTransformer()
.withFieldMapping(serialNumberMapping, creationDateMapping)
.transform(fromBean, ToBean.class);
Case 2: A destination field value (in a nested class) has to be retrieved from the source class root
The previous example highlighted how to get a value from a source object; this one instead explains how to put a value in a nested object.
Given:
public class FromBean { public class ToBean {
private final String name; private final String name;
private final FromSubBean nestedObject; private final ToSubBean nestedObject;
private final int x;
// all args constructor // all args constructor
// getters... // getters...
} }
And:
public class ToSubBean {
private final int x;
// all args constructor
} // getters...
Assuming that the value x
should be mapped into the field: With x
contained in the ToSubBean
object, the field mapping has to be defined as follow:
FieldMapping fieldMapping = new FieldMapping("x", "nestedObject.x");
Then, we just need to pass it to theTransformer
and execute the transformation:
ToBean toBean = new BeanUtils().getTransformer()
.withFieldMapping(fieldMapping)
.transform(fromBean, ToBean.class);
Different Field Names Defining Constructor Args
The mapping between different fields can also be defined by adding @ConstructorArg
annotation next to constructor arguments.
The @ConstructorArg
takes as input the name of the correspondent field in the source object.
public class FromBean { public class ToBean {
private final String name; private final String differentName;
private final int id; private final int id;
private final List<FromSubBean> subBeanList; private final List<ToSubBean> subBeanList;
private final List<String> list; private final List<String> list;
private final FromSubBean subObject; private final ToSubBean subObject;
// all args constructor
// getters...
public ToBean(@ConstructorArg("name") final String differentName,
@ConstructorArg("id") final int id,
} @ConstructorArg("subBeanList") final List<ToSubBean> subBeanList,
@ConstructorArg(fieldName ="list") final List<String> list,
@ConstructorArg("subObject") final ToSubBean subObject) {
this.differentName = differentName;
this.id = id;
this.subBeanList = subBeanList;
this.list = list;
this.subObject = subObject;
}
// getters...
}
Then:
ToBean toBean = beanUtils.getTransformer().transform(fromBean, ToBean.class);
Apply a Custom Transformation on a Specific Field Lambda Function
We know that, in real life, it’s rare that we just need to copy information between two Java Beans almost identical, often occurs that:
- The destination object has a totally different structure than the source object
- We need to perform some operation on a specific field value before copying it
- The destination object’s fields have to be validated
- The destination object has an additional field than the source object that needs to be filled with something coming from a different source
BULL gives the possibility to perform any kind of operation on a specific field, actually taking advantage of lambda expressions, the developer can define its own method that will be applied to the a value before copying it.
Let’s explain it better with an example:
Given the following Source
class:
public class FromFoo {
private final String id;
private final String val;
private final List<FromSubFoo> nestedObjectList;
// all args constructor
// getters
}
And the following Destination
class:
public class MixedToFoo {
public String id;
@NotNull
private final Double val;
// constructors
// getters and setters
}
And assuming that theval
field needs to be multiplied by a random value in our transformer, we have two problems:
- The
val
field has a different type than theSource
object, indeed one isString
and one isDouble
- We need to instruct the library on how we would apply out math operation
Well, this is pretty simple, you just need to define your own lambda expression to do that:
FieldTransformer<String, Double> valTransformer =
new FieldTransformer<>("val",
n -> Double.valueOf(n) * Math.random());
The expression will be applied to the field with the name val
in the destination object.
The last step is to pass the function the Transformer
instance:
MixedToFoo mixedToFoo = new BeanUtils().getTransformer()
.withFieldTransformer(valTransformer)
.transform(fromFoo, MixedToFoo.class);
Assign a Default Value in Case of Missing Field in the Source Object
Sometimes, this happens where the destination object has more fields than the source object; in this case, the BeanUtils
library will raise an exception informing it that they cannot perform the mapping as they do not know from where the value has to be retrieved.
A typical scenario is the following:
public class FromBean { public class ToBean {
private final String name; @NotNull
private final BigInteger id; public BigInteger id;
private final String name;
private String notExistingField; // this will be null and no exceptions will be raised
// constructors... // constructors...
// getters... // getters and setters...
} }
However, we can configure the library in order to assign the default value for the field type (e.g.0
for int
type,null
for String
, etc.)
ToBean toBean = new BeanUtils().getTransformer()
.setDefaultValueForMissingField(true)
.transform(fromBean, ToBean.class);
Applying a Transformation Function in Case of Missing Fields in the Source Object
The example below shows how to assign a default value (or a result of lambda function) on a not existing field in the source object:
public class FromBean { public class ToBean {
private final String name; @NotNull
private final BigInteger id; public BigInteger id;
private final String name;
private String notExistingField; // this will have value: sampleVal
// all args constructor // constructors...
// getters... // getters and setters...
} }
What we need to do is to assign a FieldTransformer
function to a specific field:
FieldTransformer<String, String> notExistingFieldTransformer =
new FieldTransformer<>("notExistingField", () -> "sampleVal");
The above functions will assign a fixed value to the field notExistingField
, but we can return whatever, for example, we can call an external method that returns a value obtained after a set of operation, something like:
FieldTransformer<String, String> notExistingFieldTransformer =
new FieldTransformer<>("notExistingField", () -> calculateValue());
However, in the end, we just need to pass it to the Transformer
.
ToBean toBean = new BeanUtils().getTransformer()
.withFieldTransformer(notExistingFieldTransformer)
.transform(fromBean, ToBean.class);
Apply a Transformation Function to a Specific Field in a Nested Object
Case 1: Lambda transformation function applied to a specific field in a nested class
Given:
public class FromBean { public class ToBean {
private final String name; private final String name;
private final FromSubBean nestedObject; private final ToSubBean nestedObject;
// all args constructor // all args constructor
// getters... // getters...
} }
And:
public class FromSubBean { public class ToSubBean {
private final String name; private final String name;
private final long index; private final long index;
// all args constructor // all args constructor
// getters... // getters...
} }
Assuming that the lambda transformation function should be applied only to the field name
contained in the ToSubBean
object, the transformation function has to be defined as follow:
FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("nestedObject.name", StringUtils::capitalize);
Then, pass the function to the Transformer
object:
ToBean toBean = new BeanUtils().getTransformer()
.withFieldTransformer(nameTransformer)
.transform(fromBean, ToBean.class);
Case 2: Lambda transformation function applied to a specific field independently from its location
Imagine that in our Destination
class, there are more occurrences of a field with the same name, located in different classes, and that we want to apply the same transformation function to all of them; there is a setting that allows this.
Taking, as an example, the above objects and assuming that we want to capitalize on all values contained in thename
field independently from their location, we can do the following:
FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("name", StringUtils::capitalize);
Then:
ToBean toBean = beanUtils.getTransformer()
.setFlatFieldTransformation(true)
.withFieldTransformer(nameTransformer)
.transform(fromBean, ToBean.class);
Static Transformer Function:
BeanUtils
offers a "static" version of the transformer method that can be an added value when needs to be applied in a composite lambda expression.
For example:
List<FromFooSimple> fromFooSimpleList = Arrays.asList(fromFooSimple, fromFooSimple);
The transformation should have been done by the following:
BeanTransformer transformer = new BeanUtils().getTransformer();
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
.map(fromFoo -> transformer.transform(fromFoo, ImmutableToFooSimple.class))
.collect(Collectors.toList());
Thanks to this feature, it's possible to create a transformer function specific for a given object class:
Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(ImmutableToFooSimple.class);
Then, the list can be transformed as follows:
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
.map(transformerFunction)
.collect(Collectors.toList());
However, it can happen that we have configured a BeanTransformer
instance with several fields, mapping and transformation functions, and we want to use it also for this transformation, so what we need to do is to create the transformer function from our transformer:
BeanTransformer transformer = new BeanUtils().getTransformer()
.withFieldMapping(new FieldMapping("a", "b"))
.withFieldMapping(new FieldMapping("c", "d"))
.withTransformerFunction(new FieldTransformer<>("locale", Locale::forLanguageTag));
Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(transformer, ImmutableToFooSimple.class);
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
.map(transformerFunction)
.collect(Collectors.toList());
Enable Java Bean Validation
One of the features offered by the library is bean validation. It consists of checking that the transformed object meets the constraints defined on it. The validation works with both the default javax.constraints and the custom one.
Assuming that the field id
in the FromBean
instance is null
.
public class FromBean { public class ToBean {
private final String name; @NotNull
private final BigInteger id; public BigInteger id;
private final String name;
// all args constructor // all args constructor
// getters... // getters and setters...
} }
Adding the following configuration, the validation will be performed at the end of the transformation process, and in our example, an exception will be thrown informing that the object is invalid:
ToBean toBean = new BeanUtils().getTransformer()
.setValidationEnabled(true)
.transform(fromBean, ToBean.class);
Copy on an Existing Instance
Even if the library is able to create a new instance of the given class and fill it with the values in the given object, there could be cases in which it's needed to inject the values on an already existing instance, so given the following Java Beans:
public class FromBean { public class ToBean {
private final String name; private String name;
private final FromSubBean nestedObject; private ToSubBean nestedObject;
// all args constructor // constructor
// getters... // getters and setters...
} }
If we need to perform the copy on an already existing object, we just need to pass the class instance to the transform
function:
ToBean toBean = new ToBean();
new BeanUtils().getTransformer().transform(fromBean, toBean);
Skip Transformation on a Given Set of Fields
In case we are copying source object values into an already existing instance (with some values already set), we may need to avoid that the transformation operation overrides the existing values. The below example explains how to do it, given:
public class FromBean { public class ToBean {
private final String name; private String name;
private final FromSubBean nestedObject; private ToSubBean nestedObject;
// all args constructor // constructor
// getters... // getters and setters...
} }
public class FromBean2 {
private final int index;
private final FromSubBean nestedObject;
// all args constructor
// getters...
}
If we need to skip the transformation for a set of fields, we just need to pass their name to the skipTransformationForField
method. For example, if we want to skip the transformation on the field nestedObject
, this is what we need to do:
ToBean toBean = new ToBean();
new BeanUtils().getTransformer()
.skipTransformationForField("nestedObject")
.transform(fromBean, toBean);
This feature allows transforming an object keeping the data from different sources.
To better explain this function, let's assume that the ToBean
(defined above) should be transformed as follows:
name
field value has been taken from theFromBean
objectnestedObject
field value has been taken from theFromBean2
object
The objective can be reached by doing:
// create the destination object
ToBean toBean = new ToBean();
// execute the first transformation skipping the copy of: 'nestedObject' field that should come from the other source object
new BeanUtils().getTransformer()
.skipTransformationForField("nestedObject")
.transform(fromBean, toBean);
// then execute the transformation skipping the copy of: 'name' field that should come from the other source object
new BeanUtils().getTransformer()
.skipTransformationForField("name")
.transform(fromBean2, toBean);
Field Type Conversion
In the case where a field type is different from the source class and the destination, we have this example:
public class FromBean { public class ToBean {
private final String index; private int index;
// all args constructor // constructor
// getters... // getters and setters...
} }
It can be transformed using a specific transformation function:
FieldTransformer<String, Integer> indexTransformer = new FieldTransformer<>("index", Integer::parseInt);
ToBean toBean = new BeanUtils()
.withFieldTransformer(indexTransformer)
.transform(fromBean, ToBean.class);
Transformation of Java Bean using the Builder pattern
The library supports the transformation of Java Bean using different types of Builder patterns: the standard one (supported by default) and a custom one. Let's see them in details and how to enable the custom Builder type transformation.
Let's start from the standard one supported by default:
x
public class ToBean {
private final Class<?> objectClass;
private final Class<?> genericClass;
ToBean(final Class<?> objectClass, final Class<?> genericClass) {
this.objectClass = objectClass;
this.genericClass = genericClass;
}
public static ToBeanBuilder builder() {
return new ToBean.ToBeanBuilder();
}
// getter methods
public static class ToBeanBuilder {
private Class<?> objectClass;
private Class<?> genericClass;
ToBeanBuilder() {
}
public ToBeanBuilder objectClass(final Class<?> objectClass) {
this.objectClass = objectClass;
return this;
}
public ToBeanBuilder genericClass(final Class<?> genericClass) {
this.genericClass = genericClass;
return this;
}
public com.hotels.transformer.model.ToBean build() {
return new ToBean(this.objectClass, this.genericClass);
}
}
}
As said, this requires no extra settings, so the transformation can be performed by doing:
ToBean toBean = new BeanTransformer() .transform(sourceObject, ToBean.class);
Custom Builder Pattern:
xxxxxxxxxx
public class ToBean {
private final Class<?> objectClass;
private final Class<?> genericClass;
ToBean(final ToBeanBuilder builder) {
this.objectClass = builder.objectClass;
this.genericClass = builder.genericClass;
}
public static ToBeanBuilder builder() {
return new ToBean.ToBeanBuilder();
}
// getter methods
public static class ToBeanBuilder {
private Class<?> objectClass;
private Class<?> genericClass;
ToBeanBuilder() {
}
public ToBeanBuilder objectClass(final Class<?> objectClass) {
this.objectClass = objectClass;
return this;
}
public ToBeanBuilder genericClass(final Class<?> genericClass) {
this.genericClass = genericClass;
return this;
}
public com.hotels.transformer.model.ToBean build() {
return new ToBean(this);
}
}
}
To transform the above Bean the instruction to use is:
ToBean toBean = new BeanTransformer() .setCustomBuilderTransformationEnabled(true) .transform(sourceObject, ToBean.class);
Transformation of Java Records
As of JDK 14 a new type of objects has been introduced: Java Records. Records are immutable data classes that require only the type and name of fields. The equals, hashCode, and toString methods, as well as the private, final fields, and public constructor, are generated by the Java compiler.
A Java Record defined as following:
public record FromFooRecord(BigInteger id, String name) { }
can be easily transformed into this:
public record ToFooRecord(BigInteger id, String name) { }
with a simple instruction:
ToFooRecord toRecord = new BeanTransformer().transform(sourceRecord, ToFooRecord.class);
the library is also able to transform from a Record to a Java Bean and vice versa.
4. Bean Validation
The class validation against a set of rules can be precious, especially when we need to be sure that the object data is compliant with our expectations.
The “field validation” aspect is one of the features offered by BULL and it's totally automatic — you only need to annotate your field with one of the existing javax.validation
.constraints (or defining a custom one) and then execute the validation on this.
Given the following bean:
public class SampleBean {
@NotNull
private BigInteger id;
private String name;
// constructor
// getters and setters...
}
An instance of the above object:
SampleBean sampleBean = new SampleBean();
And one line of code, such as:
new BeanUtils().getValidator().validate(sampleBean);
This will throw an InvalidBeanException
, as the field id
isnull
.
Conclusion
I have tried to explain — with examples — how to use the main features offered by the BULL project. However, looking at the complete source code might even be more helpful.
More examples can be found looking at the test cases implemented on the BULL project, available here.
GitHub also contains a sample Spring Boot project that uses the library for transforming the request/response objects among the different layers, which can be found here.
Opinions expressed by DZone contributors are their own.
Comments