 Java, Spring and Web Development tutorials  1. Overview
In this tutorial, we’ll look at how to customize our MapStruct mappers to convert empty Strings into nulls. We’ll investigate several options, each offering varying levels of control and customization.
2. Example Objects
Before we start, we need to create two objects to map between in our examples and tests. To keep things simple, we’ll use a Student as our object to map from:
class Student {
String firstName;
String lastName;
}
For our object to map into, let’s create a Teacher:
class Teacher {
String firstName;
String lastName;
}
We’ve kept things very basic here; both classes have the same properties with the same names, so by default, our mappers will work without extra annotations.
3. Global Mapper for All Strings
For our first mapper, let’s look at a solution that will cover every situation and every mapping we create. This is useful if our application always wants to convert Strings to nulls, without exception.
The basic outline of a mapper will be the same for all examples. We need an interface annotated with @Mapper, an instance of the interface so we can make use of it, and finally, our mapper method:
@Mapper
interface EmptyStringToNullGlobal {
EmptyStringToNullGlobal INSTANCE = Mappers.getMapper(EmptyStringToNullGlobal.class);
Teacher toTeacher(Student student);
}
This will already work for basic mapping. However, if we want it to convert empty Strings to nulls, we’ll need to add another mapper to the Interface:
String mapEmptyString(String string) {
return string != null && !string.isEmpty() ? string : null;
}
In this method, we’re telling MapStruct to return null if the String it’s dealing with is either already null or empty. Otherwise, it returns the original String.
MapStruct will use this method for all Strings it comes across, so if we added more mappers to our interface, they’d automatically use it as well. As long as we always want this behavior, this option is simple, automatically reusable, and requires minimal extra code. It’s also a versatile solution, as it would allow us to do many more things than the conversion we are focusing on here.
To demonstrate this working, let’s create a simple test:
@Test
void givenAMapperWithGlobalNullHandling_whenConvertingEmptyString_thenOutputNull() {
EmptyStringToNullGlobal globalMapper = EmptyStringToNullGlobal.INSTANCE;
Student student = new Student("Steve", "");
Teacher teacher = globalMapper.toTeacher(student);
assertEquals("Steve", teacher.firstName);
assertNull(teacher.lastName);
}
In this test, we’ve retrieved an instance of our mapper. Then, we’ve created a Student with the first name Steve and an empty String for their second name. After using our mapper, we can see that the created Teacher object has the first name Steve as expected. It also has a null second name, proving the mapper works as we wanted.
4. Using the @Condition Annotation
4.1. Basic @Condition Usage
The second option we’ll look at uses the @Condition annotation. This annotation allows us to create methods that MapStruct will call to check if a property should be mapped or not. As MapStruct will default non-mapped fields to null, we can use this to achieve our goal. This time, our mapper interface looks the same as before, but with a new method replacing our previous String mapper:
@Mapper
interface EmptyStringToNullCondition {
EmptyStringToNullCondition INSTANCE = Mappers.getMapper(EmptyStringToNullCondition.class);
Teacher toTeacher(Student student);
@Condition
default boolean isNotEmpty(String value) {
return value != null && !value.isEmpty();
}
}
Our method isNotEmpty() here will be called by MapStruct every time it comes across a String to process. If it returns false, it won’t map the value, and the field in the target object will be null. If it returns true, on the other hand, the value will be mapped across.
Let’s create a test to confirm this works as expected:
@Test
void givenAMapperWithConditionAnnotationNullHandling_whenConvertingEmptyString_thenOutputNull() {
EmptyStringToNullCondition conditionMapper = EmptyStringToNullCondition.INSTANCE;
Student student = new Student("Steve", "");
Teacher teacher = conditionMapper.toTeacher(student);
assertEquals("Steve", teacher.firstName);
assertNull(teacher.lastName);
}
The test is almost identical to the one we wrote for the last option we looked at. We are simply using the new mapper and checking that we get the same results.
4.2. Checking Target and Source Property Names
The @Condition annotation option is more customizable than our previous global mapper example. We can add two extra annotated arguments, targetPropertyName and sourcePropertyName, to our isNotEmpty() method signature:
@Condition
boolean isNotEmpty(String value, @TargetPropertyName String targetPropertyName, @SourcePropertyName String sourcePropertyName) {
if (sourcePropertyName.equals("lastName")) {
return value != null && !value.isEmpty();
}
return true;
}
These new arguments give us the field names we are mapping to and from. We can use them to give special treatment to some Strings, and perhaps exclude others from our checks entirely. In this example, we’ve specifically looked for a source property called lastName and only applied our checks when we find it. This option is useful if we want to apply the empty String to null mapping for most cases and have a few exceptions, or we only rarely want to apply it.
If there are a lot of exceptions or fields we want to look for, the code would get too messy, and we should look at another way of doing this. This option is also limited to the field in the created object being null or the source value; we can’t alter the value at all.
5. Using an Expression
Finally, let’s look at the most targeted option. We can use an expression in our mapper to affect a single mapping method at a time. So, this approach is optimal if this is not a behavior we want to use widely. To use an expression, we simply add it to the @Mapping annotation on our method. Our mapper interface will look like:
@Mapper
public interface EmptyStringToNullExpression {
EmptyStringToNullExpression INSTANCE = Mappers.getMapper(EmptyStringToNullExpression.class);
@Mapping(target = "lastName", expression = "java(student.lastName.isEmpty() ? null : student.lastName)")
Teacher toTeacher(Student student);
}
This option gives us a lot of power. We’ve defined the target field we are interested in and provided Java code to tell MapStruct how to map it across. Our Java checks if the lastName field is empty; if so, it returns null, else it returns the original lastName.
The benefit and the drawback here are that we’ve been very specific about what field this will affect. The firstName field will not be handled at all, let alone any Strings in other mappers we might define. This is, therefore, an ideal option for applications where we only very rarely want to map our Strings like this.
Finally, let’s test the expression-based option and confirm it works:
@Test
void givenAMapperUsingExpressionBasedNullHandling_whenConvertingEmptyString_thenOutputNull() {
EmptyStringToNullExpression expressionMapper = EmptyStringToNullExpression.INSTANCE;
Student student = new Student("Steve", "");
Teacher teacher = expressionMapper.toTeacher(student);
assertEquals("Steve", teacher.firstName);
assertNull(teacher.lastName);
}
Again, we’ve tested the same thing, just using our expression mapper. Once again, the lastName field is mapped to null as expected, while firstName remains intact.
6. Conclusion
In this article, we’ve looked at three options for using MapStruct to convert Strings to nulls. First, we saw that using a global mapper is a simple option if we always want this behavior without exception. We then looked at using the @Condition annotation to get similar results, but with the option for more control if we need it. Finally, we saw how we can use an expression to target individual fields if we only rarely want a certain mapping to occur.
As always, the full code for the examples is available over on GitHub. The post How to Map an Empty String to Null Using MapStruct first appeared on Baeldung.
Content mobilized by FeedBlitz RSS Services, the premium FeedBurner alternative. |