Java Bean to Java Bean mapper that recursively copies data from one object to another
—
Extensible converter framework for handling complex type transformations and custom mapping logic that goes beyond Dozer's automatic property mapping capabilities.
Base interface for implementing custom conversion logic.
/**
* Public custom converter interface for custom data mapping logic
*/
public interface CustomConverter {
/**
* Converts source field value to destination field value
* @param existingDestinationFieldValue current value of destination field (may be null)
* @param sourceFieldValue value from source field
* @param destinationClass target class type
* @param sourceClass source class type
* @return converted value for destination field
*/
Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
Class<?> destinationClass, Class<?> sourceClass);
}Usage Example:
import com.github.dozermapper.core.CustomConverter;
import com.github.dozermapper.core.ConversionException;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
public class DateToStringConverter implements CustomConverter {
@Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
Class<?> destinationClass, Class<?> sourceClass) {
if (sourceFieldValue == null) {
return null;
}
if (sourceFieldValue instanceof Date && destinationClass == String.class) {
return new SimpleDateFormat("yyyy-MM-dd").format((Date) sourceFieldValue);
}
if (sourceFieldValue instanceof String && destinationClass == Date.class) {
try {
return new SimpleDateFormat("yyyy-MM-dd").parse((String) sourceFieldValue);
} catch (ParseException e) {
throw new ConversionException("Invalid date format: " + sourceFieldValue, e);
}
}
throw new ConversionException("Unsupported conversion from " +
sourceClass.getName() + " to " + destinationClass.getName());
}
}Extended converter interface that can receive configuration parameters.
/**
* Custom converter that can receive configuration parameters
*/
public interface ConfigurableCustomConverter extends CustomConverter {
/**
* Sets configuration parameter for the converter
* @param parameter configuration string
*/
void setParameter(String parameter);
}Usage Example:
import com.github.dozermapper.core.ConfigurableCustomConverter;
import java.text.SimpleDateFormat;
public class DateFormatConverter implements ConfigurableCustomConverter {
private String dateFormat = "yyyy-MM-dd"; // default
@Override
public void setParameter(String parameter) {
this.dateFormat = parameter;
}
@Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
Class<?> destinationClass, Class<?> sourceClass) {
// Use this.dateFormat for conversion
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
// ... conversion logic
return null; // Placeholder for actual conversion logic
}
}Abstract base class providing type-safe bidirectional conversion.
/**
* Base class for implementing type-safe bidirectional custom converters
* @param <A> first type for conversion
* @param <B> second type for conversion
*/
public abstract class DozerConverter<A, B> implements ConfigurableCustomConverter {
/**
* Constructor specifying the types this converter handles
* @param prototypeA class of type A
* @param prototypeB class of type B
*/
public DozerConverter(Class<A> prototypeA, Class<B> prototypeB);
/**
* Convert from type A to type B
* @param source source object of type A
* @param destination existing destination object of type B (may be null)
* @return converted object of type B
*/
public abstract B convertTo(A source, B destination);
/**
* Convert from type B to type A
* @param source source object of type B
* @param destination existing destination object of type A (may be null)
* @return converted object of type A
*/
public abstract A convertFrom(B source, A destination);
/**
* Convert from type A to type B (creates new instance)
* @param source source object of type A
* @return new converted object of type B
*/
public B convertTo(A source);
/**
* Convert from type B to type A (creates new instance)
* @param source source object of type B
* @return new converted object of type A
*/
public A convertFrom(B source);
/**
* Sets configuration parameter for the converter
* @param parameter configuration string
*/
public void setParameter(String parameter);
/**
* Gets the configuration parameter
* @return configuration string or null if not set
*/
public String getParameter();
}Usage Example:
public class MoneyToStringConverter extends DozerConverter<Money, String> {
public MoneyToStringConverter() {
super(Money.class, String.class);
}
@Override
public String convertTo(Money source, String destination) {
if (source == null) return null;
return source.getAmount() + " " + source.getCurrency();
}
@Override
public Money convertFrom(String source, Money destination) {
if (source == null) return null;
String[] parts = source.split(" ");
return new Money(new BigDecimal(parts[0]), parts[1]);
}
}Interface allowing converters to receive mapper instance injection for recursive mapping.
/**
* Allows custom converters to receive mapper instance injection
*/
public interface MapperAware {
/**
* Injects the mapper instance into the converter
* @param mapper the mapper instance
*/
void setMapper(Mapper mapper);
}Usage Example:
public class PersonToPersonDtoConverter extends DozerConverter<Person, PersonDto>
implements MapperAware {
private Mapper mapper;
public PersonToPersonDtoConverter() {
super(Person.class, PersonDto.class);
}
@Override
public void setMapper(Mapper mapper) {
this.mapper = mapper;
}
@Override
public PersonDto convertTo(Person source, PersonDto destination) {
if (source == null) return null;
PersonDto result = destination != null ? destination : new PersonDto();
result.setFullName(source.getFirstName() + " " + source.getLastName());
// Use injected mapper for nested objects
if (source.getAddress() != null) {
result.setAddress(mapper.map(source.getAddress(), AddressDto.class));
}
return result;
}
@Override
public Person convertFrom(PersonDto source, Person destination) {
// Reverse conversion logic
return null; // Implementation details...
}
}Register converters to be used for all applicable type combinations:
Mapper mapper = DozerBeanMapperBuilder.create()
.withCustomConverter(new DateToStringConverter())
.withCustomConverter(new MoneyToStringConverter())
.build();Register converters with IDs for specific mapping configurations:
Mapper mapper = DozerBeanMapperBuilder.create()
.withCustomConverterWithId("dateConverter", new DateFormatConverter())
.withCustomConverterWithId("moneyConverter", new MoneyToStringConverter())
.build();Then reference in XML mapping:
<field>
<a>dateField</a>
<b>stringField</b>
<a-converter type="dateConverter" parameter="MM/dd/yyyy" />
</field>Dozer includes several built-in converters for common scenarios:
DateConverter: Handles various Date type conversionsCalendarConverter: Calendar to other date type conversionsInstantConverter: Java 8 Instant conversionsIntegerConverter: Integer and int conversionsEnumConverter: Automatic enum to string and vice versapublic class ConditionalConverter implements CustomConverter {
@Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
Class<?> destinationClass, Class<?> sourceClass) {
// Only convert if certain conditions are met
if (shouldConvert(sourceFieldValue, destinationClass)) {
return performConversion(sourceFieldValue, destinationClass);
}
// Return existing value or null
return existingDestinationFieldValue;
}
private boolean shouldConvert(Object source, Class<?> destClass) {
// Custom logic to determine if conversion should occur
return source != null && isValidForConversion(source);
}
}public class CollectionElementConverter extends DozerConverter<List<String>, List<Integer>> {
public CollectionElementConverter() {
super(List.class, List.class);
}
@Override
public List<Integer> convertTo(List<String> source, List<Integer> destination) {
if (source == null) return null;
List<Integer> result = new ArrayList<>();
for (String str : source) {
result.add(Integer.valueOf(str));
}
return result;
}
@Override
public List<String> convertFrom(List<Integer> source, List<String> destination) {
if (source == null) return null;
List<String> result = new ArrayList<>();
for (Integer num : source) {
result.add(num.toString());
}
return result;
}
}Custom converters should handle errors appropriately:
/**
* Exception thrown during conversion operations
*/
public class ConversionException extends MappingException {
public ConversionException(String message);
public ConversionException(String message, Throwable cause);
public ConversionException(Throwable cause);
}Usage in Converters:
@Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
Class<?> destinationClass, Class<?> sourceClass) {
try {
// Conversion logic here
return performConversion(sourceFieldValue);
} catch (Exception e) {
throw new ConversionException("Failed to convert " + sourceFieldValue +
" to " + destinationClass.getSimpleName(), e);
}
}DozerConverter<A, B> for type safety when possibleGlobal field mapping interface for intercepting all field mappings. Should be used very sparingly as it impacts performance.
/**
* Public custom field mapper interface. A custom field mapper should only be used in very rare
* and unusual cases because it is invoked for ALL field mappings. For custom mappings of
* particular fields, using a CustomConverter is a much better choice.
*/
public interface CustomFieldMapper {
/**
* Custom field mapping logic invoked for all field mappings
* @param source source object
* @param destination destination object
* @param sourceFieldValue value from source field
* @param classMap internal class mapping metadata
* @param fieldMapping internal field mapping metadata
* @return true if field was handled, false to continue with normal mapping
*/
boolean mapField(Object source, Object destination, Object sourceFieldValue,
ClassMap classMap, FieldMap fieldMapping);
}Usage Example:
import com.github.dozermapper.core.CustomFieldMapper;
import com.github.dozermapper.core.classmap.ClassMap;
import com.github.dozermapper.core.fieldmap.FieldMap;
public class GlobalAuditFieldMapper implements CustomFieldMapper {
@Override
public boolean mapField(Object source, Object destination, Object sourceFieldValue,
ClassMap classMap, FieldMap fieldMapping) {
// Handle audit fields globally
if ("lastModified".equals(fieldMapping.getDestFieldName())) {
// Set current timestamp instead of source value
ReflectionUtils.setFieldValue(destination, "lastModified", new Date());
return true; // Field handled, skip normal mapping
}
if ("modifiedBy".equals(fieldMapping.getDestFieldName())) {
// Set current user instead of source value
ReflectionUtils.setFieldValue(destination, "modifiedBy", getCurrentUser());
return true; // Field handled
}
return false; // Continue with normal mapping
}
}
// Configuration
Mapper mapper = DozerBeanMapperBuilder.create()
.withCustomFieldMapper(new GlobalAuditFieldMapper())
.build();Install with Tessl CLI
npx tessl i tessl/maven-com-github-dozermapper--dozer-core