Data Masking is a common requirement in any new system. This 5-minute example will show how you can easily mask sensitive data in Java with mapstruct.

Why Mask Data

There are rules and regulations around what developers should be able to see when working with sensitive data. However, if you are to successfully support any system, you need that data available to scrutinize when you have problems.

The best way to achieve both is to mask the sensitive parts of any data, leaving enough of it available to support the system.

Get rid of too much and you won’t be able to work out what is happening.

Leave any sensitive data unmasked, and you’ll be in big trouble.

Mapstruct Makes Data Masking in Java easy

It’s generally a bit of a tedious job masking data with plenty of custom solutions based on other architectural components such as Splunk or log4j.

I think it’s much easier to use a MapStruct class to do any data hiding for you.

It takes a simple object and transforms it, masking data as it goes.

Understanding The Example

We’re going to take a simple customer details object.

{
  "firstName": "John",
  "middleName": "A",
  "lastName": "Dobie",
  "personalDetails": {
    "niNumber": "NS1234567",
    "dateOfBirth": "1970-01-01"
  }
}

Were then going to use mapstruct to mask the data as below.

  • Hide the middle name by setting it to null
  • Set national insurance number to ********
  • Set date of birth to ****-**-**

I’ve used a minimal example to show the technique.
I’ve used a sub-object to show how easy it is to mask hierarchical data.

Running The Example

The following commands will allow you to check out and run the example.

git clone https://github.com/johndobie/masking.git
mvn clean install spring-boot:run

curl http://localhost:8080/customer
{
  "firstName": "John",
  "middleName": "A",
  "lastName": "Dobie",
  "personalDetails": {
    "niNumber": "NS1234567",
    "dateOfBirth": "1970-01-01"
  }
}

curl http://localhost:8080/customer/masked
{
  "firstName": "John",
  "middleName": null,
  "lastName": "Dobie",
  "personalDetails": {
    "niNumber": "********",
    "dateOfBirth": "****-**-**"
  }
}

Building The Example

PersonalDetails.java

A simple class containing some details which we will want to mask.

@Data
@Builder
public class PersonalDetails {

    private String niNumber;
    private String dateOfBirth;
}
PersonalDetails.java

CustomerDetails.java

A class containing more basic details and the personal details we want to mask.

@Data
@Builder
public class CustomerDetails {
    
    private String firstName;
    private String middleName;
    private String lastName;
    
    private PersonalDetails personalDetails;
}

Create A Mapstruct Transformer To Mask The Fields.

The mapstruct object is a simple interface which you define below.
The mappings tell mapstruct what to do with the fields. By default, they are just copied across.

@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN)
public interface CustomerMaskTransformer {

    public static final String DOB_DEFAULT = "****-**-**";
    public static final String NI_DEFAULT = "********";

    @Mapping(target="middleName", ignore = true)
    @Mapping(target="personalDetails.niNumber", qualifiedByName = "niNumber")
    @Mapping(target="personalDetails.dateOfBirth", qualifiedByName = "dateOfBirth")
    CustomerDetails getMaskedCustomer(CustomerDetails customerDetails);

    @Named("dateOfBirth")
    default String maskDateOfBirth(String dob){
        return DOB_DEFAULT;
    }

    @Named("niNumber")
    default String maskNationalInsuranceNumber(String nationalInsuranceNumber){
        return NI_DEFAULT;
    }
}

Mapstruct will automatically generate an implementation class which will do the underlying transformations.

We have marked the ones we want to mask with a mapping. These mappings are explained in further detail below.

Removing Middle Name

The following line will make sure that the middle name is hidden.
Mapstruct will ignore it in the source object, so it will be null in the target one.

@Mapping(target="middleName", ignore = true)

Masking NI Number

The following lines will take the ni number and convert it to “********”

public static final String NI_DEFAULT = "********";

@Mapping(target="personalDetails.niNumber", qualifiedByName = "niNumber")

@Named("niNumber")
default String maskNationalInsuranceNumber(String nationalInsuranceNumber){
    return NI_DEFAULT;
}

Masking Date of Birth

Finally, the following lines will set the date of birth to “****-**-**”

public static final String DOB_DEFAULT = "****-**-**";

@Mapping(target="personalDetails.dateOfBirth", qualifiedByName = "dateOfBirth")

@Named("dateOfBirth")
default String maskDateOfBirth(String dob){
    return DOB_DEFAULT;
}

Testing The Transformer

We can use the transformer in a test to make sure the fields are being masked correctly.

The following line does the transformation – it couldn’t be any easier!

Customer maskedCustomer = customerMaskTransformer.getMaskedCustomer(customer);

We can use that in a test and make sure the masks are being applied.

@Test
void testPersonalDetailsAreCorrect() {

    CustomerDetails customerDetails = getCustomerDetails();
    CustomerDetails maskedCustomerDetails = customerMaskTransformer.getMaskedCustomer(customerDetails);

    // Check the main fields are not modified
    assertThat(maskedCustomerDetails.getFirstName()).isEqualTo(customerDetails.getFirstName());
    assertThat(maskedCustomerDetails.getLastName()).isEqualTo(customerDetails.getLastName());

    // Check the 3 fields are masked
    assertThat(maskedCustomerDetails.getMiddleName()).isNull();
    assertThat(maskedCustomerDetails.getPersonalDetails().getNiNumber()).isEqualTo(NI_DEFAULT);
    assertThat(maskedCustomerDetails.getPersonalDetails().getDateOfBirth()).isEqualTo(DOB_DEFAULT);
}

Using it in a Microservice.

We can also create a simple Spring controller and demonstrate its use.

We can use one endpoint to return the original object and another to return the masked object.

@RestController
public class CustomerController {

   static final CustomerMaskTransformer cmt = 
       mapppers.getMapper(CustomerMaskTransformer.class);

    @GetMapping("/customer")
    public CustomerDetails getCustomerEndpoint(){
        return getCustomer();
    }

    @GetMapping("/customer/masked")
    public CustomerDetails getMaskedCustomerEndpoint(){
        return cmt.getMaskedCustomer(getCustomer());
    }

    private CustomerDetails getCustomer() {
        PersonalDetails personalDetails = PersonalDetails.builder()
                .niNumber("NS1234567")
                .dateOfBirth("1970-01-01")
                .build();

        CustomerDetails customerDetails 
           = CustomerDetails.builder()
                .personalDetails(personalDetails)
                .firstName("John")
                .middleName("A")
                .lastName("Dobie")
                .build();

        return customerDetails;
    }
}

Viewing The Results

You can point your browser at the following

http//localhost:8080/customer

http://localhost:8080/customer/masked

Summary.

This was a very simple example of using mapstruct to mask data in Java.
Hopefully, it shows how quick and easy it is to use this technique.

References

https://mapstruct.org/documentation/stable/reference/html/