Skip to main content

How to use Java DTOs to stay secure

wordpress-sync/feature-java-dto-1

2022年10月11日

0 分で読めます

Data Transfer Objects (DTOs) in Java are objects that transport data between subsystems. It is an enterprise design pattern to aggregate data. The main purpose is to reduce the number of system calls needed between the subsystems, reducing the amount of overhead created.

In this article, I will explain how DTOs are used in modern Java applications, ways your application can benefit, and how Java DTOs can help you be more secure by preventing accidental data leaks.

What is a POJO, Java Bean, and Value Object

As the name already suggested, a Plain Old Java Object (POJO) is an ordinary Java Object. It can be any class and isn’t bound to any specific restrictions other than the ones prescribed by the Java language. They are created for re-usability and increased readability.

1public class CoffeePOJO {
2
3   public String name;
4   private List<String> ingredients;
5
6   public CoffeePOJO(String name, List<String> ingredients) {
7       this.name = name;
8       this.ingredients = ingredients;
9   }
10
11   void addIngredient(String ingredient) {
12       ingredients.add(ingredient);
13   }
14}

A Java Bean is a POJO according to the JavaBean standard. According to this standard, all properties are private, and will be accessed with getter and setter methods. Additionally a no-arg constructor should be present, along with a few more things.

So, this means that while all Java Beans are POJOs, not all POJOs are Java Beans.

1public class CoffeeBEAN implements Serializable {
2
3   private String name;
4   private List<String> ingredients;
5
6   public CoffeeBEAN() {
7   }
8
9   public String getName() {
10       return name;
11   }
12
13   public void setName(String name) {
14       this.name = name;
15   }
16
17   public List<String> getIngredients() {
18       return ingredients;
19   }
20
21   public void setIngredients(List<String> ingredients) {
22       this.ingredients = ingredients;
23   }
24}

A Value Object is a small object that represents a simple data entity. However, a value object doesn’t typically have an identity. Value objects are not currently available in Java, but JDK maintainers are working to add them as part of JEP 401. For now, we have to create a POJO to do the work for us.

Implementing a Data Transfer Object

A DTO can be implemented as a POJO — or a Java Bean for that matter. The most important thing is that a DTO separates concerns between entities like the presentation layer and the domain model, for example.

Let's take a small rest service to explain this. Say we have a coffee store with coffees and customers. Both of these are separate domain entities in the system. If I want to know a customer’s favorite coffee, I’ll create an API that provides the aggregate data represented in the FavoriteCoffeDTO.

wordpress-sync/blog-java-dto-diagram

The code looks something like this:

1​​public class Coffee {
2
3   private Long id;
4   private String name;
5   private List<String> ingredients;
6   private String preparation;
7
8}
9
10public class Customer {
11
12   private Long id;
13   private String firstName;
14   private String lastName;
15   private List<Coffee> coffees;
16
17}
18
19public class FavoriteCoffeeDTO {
20
21   private String name;
22   private List<String> coffees;
23
24}

I separated the domain layer from the presentation layer in the implementation above, allowing my controller to now handle mapping the two domain entities to the DTO.

In this example, I made the fields private, meaning I have to create getter and setter methods to access the fields. Users often choose to follow the JavaBean standard either completely or partially for DTO. This isn’t mandatory of course, you’re free to choose whatever method suits your needs. Other options include making all fields public and accessing them directly (like the example below), or making the object immutable with an all-args constructor and some getter methods.

1public class FavoriteCoffeeDTO {
2
3   public String name;
4   public List<String> coffees;
5
6}
7
8String name = favCoffeeDTO.name;

Lastly, if you updated to a more recent version of Java, you might want to use Java records for your DTO’s. Java Records are simple immutable classes that automatically provide you with an all-args constructor, access methods, toString(), and hashCode() without defining them. This makes your code less verbose and more readable. Notice that records do not follow the Java Bean specification, since the access methods do not have a get or set prefix.

1public record FavoriteCoffeeDTO(String name, List<String> coffees) {}
2
3String name = favoriteCoffeeDTO.name();

What makes a good DTO?

The purpose of a DTO is to carry data between processes. Therefore, a good DTO only contains the information needed for that specific part of the system. In our API example, we only need to return the name of the customer and their favorite coffee order. There is no need to add anything else, such as business logic. The general advice is to keep your DTOs as simple, small, and straightforward as possible.

Also after a DTO is initialized, its state shouldn't change or evolve. This means that an immutable data structure would be a great fit for a DTO. As a DTO only carries data that should be unaltered, a Java Record would be a great fit — especially because JSON serialization libraries like Jackson support Java Records.

DTO security considerations

We already noticed that we decouple the Domain model from the presentation layer with this DTO pattern. Simple DTOs that only contain the data needed for this subsystem or API, without any business logic, can also improve your security.

What we see in many proofs of concepts is that domain entries are fully outputted. This can lead to unnecessary data being available outside of the system and potential data leaks.

Say our API has a function to find all customers, but does not use a DTO:

1@GetMapping("/customers")
2public List<Customer> getAllCustomers(){
3   return repository.findAll();       
4}

If our customer object is the same as in our previous example, we are already displaying too much information. Do we actually need the id or the list of favorite coffees? It gets worse if we decide to attach more information to the customer, like a home address.

1public class Customer {
2   private Long id;
3   private String firstName;
4   private String lastName;
5   private List<Coffee> coffees;
6   Private String homeAddress;
7
8}

If we don't filter in our endpoint, we suddenly create a data breach, since providing a full name and home address is considered a privacy breach in many countries. Decoupling the API from the data model with a DTO would have prevented this because the mapper controller would only populate necessary fields in the DTO. Even when we decide to add something to our domain model afterwards, using a DTO prevents us from essentially leaking personally identifiable information.

1public class CustomerDTO {
2   private String firstName;
3   private String lastName;
4}

Recommendations

Using DTOs in Java to decouple subsystems is generally a good idea. From an engineering perspective, it will reduce roundtrips between the different layers and form a security angle it will help prevent accidental data leaks. In terms of security, I would recommend making your DTOs specific and limiting the amount of reuse. If you are reusing your DTOs for different functions, you should clearly understand where these DTO’s are used before changing them.

In general, keep your DTOs concise, free of business logic if possible, and only provide the data needed for specific functions. Lastly, I believe that immutability is a natural fit for DTOs, making Java Records — which was fully released in Java 16 — a great way to implement DTOs in Java.

Check out the following resources to learn more about Java security:

You can also try our free online Java code checker tool to see how the Snyk code engine analyses your code for security and quality issues.

FAQ

What is a DTO?

DTO stands for Data Transfer Object. It is an object that transports data between different processes in an application. A DTO is specifically designed to reduce the number of method calls between client and server by aggregating data. It also prevents tight coupling between entities, such as the domain model and presentation layer of an application.

What is a DTO in Java?

A Data Transfer Object in Java is typically a POJO (Plain Old Java Object) without any business logic. They usually have a flat data structure that specifically suits the purpose of the transaction between the methods it is used for. DTO’s in Java might follow the JavaBean convention with private fields and getter and setter methods, but it doesn't have too.

Is a DTO the same as a POJO?

In short, a DTO is a POJO but not every POJO is a DTO. A Data Transfer Object is a specific POJO to transport data between processes.

Is using DTOs a good practice?

Yes, if DTOs are implemented well, they reduce tight coupling and method calls — which improves security and reduces data leaks.

What is the difference between a DAO and DTO?

DAO stands for Data Access Object, and is used in a pattern to decouple business logic from the persistence layer. DAOs are often used for CRUD operations like update, delete, and save. A DTO is an object meant to exclusively transport data between subsystems and should not contain business logic.