Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

DTOs versus Entities: Drawing the Service Boundary

Posted on September 10, 2006 By Luis Fernandez

DTOs versus Entities keeps coming up in hallway chats right now. Java EE 5 just landed with JPA baked in, Hibernate keeps powering along, and Spring is everywhere in our projects. We are wiring services, exposing remote calls over EJB or SOAP, maybe flirting with REST, and we want clean, fast, simple code. The question is simple to ask and tricky to answer: where do we draw the service boundary, and what crosses it? Do we send JPA entities over the wire, or do we send Data Transfer Objects that are plain and boring and safe?

Definitions

JPA entity: a persistent class managed by an EntityManager. It carries identity, relationships, and usually lazy collections. It is often proxied by the provider and tied to a persistence context for change tracking.

DTO: a plain object with only data. No persistence vibes, no lazy loading, no proxies, usually serializable out of the box. It exists to cross a service boundary and to pin down a contract.

Service boundary: the line where a call becomes a remote call or a call that you treat as remote in your design. Think EJB remote, SOAP over HTTP, or a controller that marshals to JSON or XML for another process. Performance, versioning, and security concerns change right at that line.

Examples

Let us ground this with a tiny order domain. Here is a JPA entity that you would keep inside your service code.

import javax.persistence.*;
import java.util.*;

@Entity
@Table(name = "orders")
public class Order {
  @Id @GeneratedValue
  private Long id;

  private String number;

  @ManyToOne(fetch = FetchType.LAZY)
  private Customer customer;

  @OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  private List<OrderLine> lines = new ArrayList<>();

  // getters and setters
}

Here is a DTO shaped for a consumer. No lazy fields. Only what the client needs.

import java.io.Serializable;
import java.util.List;

public class OrderDto implements Serializable {
  private Long id;
  private String number;
  private String customerName;
  private List<LineDto> lines;

  public static class LineDto implements Serializable {
    public String sku;
    public int qty;
  }

  // getters and setters
}

And a tiny mapper. Keep it boring and testable.

public final class OrderMapper {
  private OrderMapper() {}

  public static OrderDto toDto(Order o) {
    OrderDto dto = new OrderDto();
    dto.setId(o.getId());
    dto.setNumber(o.getNumber());
    dto.setCustomerName(o.getCustomer().getName()); // force load in transaction
    List<OrderDto.LineDto> lines = new ArrayList<>();
    for (OrderLine l : o.getLines()) {
      OrderDto.LineDto ld = new OrderDto.LineDto();
      ld.sku = l.getSku();
      ld.qty = l.getQty();
      lines.add(ld);
    }
    dto.setLines(lines);
    return dto;
  }
}

Now expose a service. The contract talks DTO. Your persistence and proxies stay inside.

import javax.jws.WebService;
import javax.ejb.Stateless;
import javax.persistence.*;

@WebService
@Stateless
public class OrderService {
  @PersistenceContext EntityManager em;

  public OrderDto findByNumber(String number) {
    Order o = em.createQuery(
      "select o from Order o join fetch o.customer where o.number = :n", Order.class)
      .setParameter("n", number)
      .getSingleResult();
    // optionally fetch lines to avoid lazy problems
    o.getLines().size();
    return OrderMapper.toDto(o);
  }
}

Counterexamples

There are times when returning entities is fine. If your web tier runs in the same JVM and the call is truly local, you can return entities to a JSF or Spring MVC controller and render a page right away. The persistence context is still around inside the request, lazy loading works, and you avoid extra mapping code. This is common in monolithic web apps that do not cross a process boundary.

There are also times when sending entities blows up. The first call works in a test, then production throws a LazyInitializationException or a serialization error because a provider proxy slips into the payload. Or you expose sensitive fields by accident. Or a client updates an entity graph and you trigger unexpected writes on merge.

Decision rubric

  • Is the boundary remote now or very likely soon? Use DTO.
  • Do you need a stable contract that you can version without breaking clients? Use DTO.
  • Do you need to trim the payload to avoid dragging a whole graph across the wire? Use DTO.
  • Are you worried about lazy loading, proxies, or provider classes in your payload? Use DTO.
  • Is the call local inside one request and you own both caller and callee? Returning entities is fine.
  • Do you rely on automatic dirty checking across method calls in the same transaction? Returning entities inside the service is fine.
  • Do you run batch jobs or internal modules where the code is in the same process and the team is the same? Entities are fine.

One more practical point. If you find yourself adding serialization to an entity or sprinkling eager fetch everywhere only to satisfy a remote client, you already crossed the line. That is DTO time.

Lesson learned

Keep entities inside the service boundary. Let them do what they do best inside a transaction. At the edge of your service, map to DTO and speak a clear contract. That keeps lazy loading honest, keeps payloads small, and keeps your API from leaking internal choices. If you are in a local only setup and you control both sides, return entities and move on. The day you add a remote client, add the DTO layer at the boundary and sleep better.

JPA gives us nice tools with annotations, queries, and clean programming. The trick is not to confuse a persistence model with an API model. Draw the boundary, be explicit, and write tiny mappers that are easy to test. Boring code pays rent.

General Software Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes