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

Lazy Loading Explained: Performance without Surprises

Posted on July 13, 2014 By Luis Fernandez

Lazy loading sounds like free speed until it bites you in production.

Let’s walk through Hibernate lazy loading so your pages feel fast without waking up a surprise army of queries.

Everyone is chasing snappy pages this week. My feed is full of folks playing with Java 8 and new toys, and mobile users keep reminding us that round trips are not cheap. In that spirit, lazy loading is the friendly promise from Hibernate and JPA that says we can postpone work until we truly need it. Collections are lazy by default in Hibernate, and many to one is often eager in plain JPA unless you ask for lazy. That mix leads many teams to think they are covered, then a view touches one more association and boom, the app fires a train of queries. The idea is sound. Only fetch what you need. The trouble starts when the code path changes slightly and the ORM pulls data behind your back. Performance without surprises is about knowing what will be loaded and when, not trusting defaults to do magic.

// Typical mapping with explicit fetch types
@Entity
public class Order {
  @Id Long id;

  @ManyToOne(fetch = FetchType.LAZY)  // do not accept eager by accident
  private Customer customer;

  @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
  private Set<OrderItem> items = new HashSet<>();
}

// Access pattern inside a transaction
Order o = em.find(Order.class, id);
// Accessing o.getCustomer().getName() will trigger a lazy load of Customer
// Accessing o.getItems().size() will trigger a lazy load of OrderItem collection

The first trap is the N+1 query problem. You load 50 orders, then your view touches the customer of each order, and you just made 51 queries. It feels harmless when you test with five rows, then your feed lights up in production and you do not know why the page got slow. The second trap is the LazyInitializationException. That one arrives when the session is already closed and a view tries to walk a proxy. Folks patch it with Open Session in View and call it a day. That band aid keeps pages alive but it also makes the query count depend on template hints, which is a recipe for noise. A better habit is to shape queries for the scenario, and be explicit about what you fetch. Join fetch can load the graph you need in one round trip, and batch size can make a big difference for to one and collections that you touch many times in a loop. Measure with SQL logs and you will see the pattern clearly.

// Fetch the graph you need
// Avoid blind iteration that triggers N+1
List<Order> orders = em.createQuery(
  "select o from Order o " +
  "join fetch o.customer " +     // fetch to one
  "left join fetch o.items " +   // fetch collection
  "where o.status = :status", Order.class)
  .setParameter("status", Status.NEW)
  .getResultList();

// Helpful settings for debugging and batching in Hibernate
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.default_batch_fetch_size", "25");

// Programmatic hint with JPA 2.1 EntityGraph
EntityGraph<Order> graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("customer");
graph.addSubgraph("items").addAttributeNodes("product");
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.fetchgraph", graph);
Order o = em.find(Order.class, id, hints);

Let us talk about predictability. Teams get in trouble when views reach into entities without a clear contract. You can keep control by scoping access to the fetch plan your use case needs. A simple approach is to make repository methods return either a fully fetched entity for that screen or a thin DTO projection that never lazy loads. This way the code that knows what the page needs is the code that decides the query. If you are on Spring, keep the transaction at the service layer and do the fetch join in the query method so the view does not decide database behavior. For pieces that change often, JPA EntityGraph is a sweet middle ground because it reads like a fetch plan without scattering join strings. For fields where you really want lazy semantics, Hibernate supports bytecode enhancement so even to one and basic attributes can be pulled lazily with discipline, though you should be honest about the cost of surprises and use it with care.

// DTO projection that never lazy loads
public class OrderSummary {
  public final Long id;
  public final String customerName;
  public final BigDecimal total;

  public OrderSummary(Long id, String customerName, BigDecimal total) {
    this.id = id; this.customerName = customerName; this.total = total;
  }
}

List<OrderSummary> summaries = em.createQuery(
  "select new com.acme.OrderSummary(o.id, c.name, sum(i.price)) " +
  "from Order o join o.customer c join o.items i " +
  "where o.status = :status group by o.id, c.name", OrderSummary.class)
  .setParameter("status", Status.NEW)
  .getResultList();

Field level lazy with bytecode enhancement is available in Hibernate. It lets you map FetchType.LAZY on to one and even basic attributes. It needs build time tooling, and you still must keep an eye on the session boundary to avoid lazy loads from views.

<!-- Maven plugin for Hibernate bytecode enhancement -->
<plugin>
  <groupId>org.hibernate.orm.tooling</groupId>
  <artifactId>hibernate-enhance-maven-plugin</artifactId>
  <version>4.3.6.Final</version>
  <executions>
    <execution>
      <goals>
        <goal>enhance</goal>
      </goals>
      <configuration>
        <enableLazyInitialization>true</enableLazyInitialization>
      </configuration>
    </execution>
  </executions>
</plugin>

Two quick tips before you ship. First, turn on SQL logging and count queries for the page in question. If a simple table view is doing fifty selects, you have work to do. Second, add a test that trips the most common path for that page and asserts a cap on the number of statements. You can wire p6spy or datasource proxy and fail the build if the number climbs. It is a small guardrail that pays for itself the first time someone adds a new column to a view and silently triggers a cascade of lazy loads. Also, be deliberate with the cache. Second level cache can hide some of the pain in staging, then break in prod if nodes do not share state. Better to tighten the query and then add cache if it still matters.

// Spring style test that watches query count with datasource-proxy
// Pseudocode to illustrate the idea
@Test
public void order_list_page_stays_under_ten_queries() {
  QueryCounter.reset();
  List<Order> viewModel = orderService.loadOrdersForList(); // triggers queries
  assertTrue("Too many queries: " + QueryCounter.count(), QueryCounter.count() <= 10);
}

Last bit of street advice. Open Session in View is handy during scaffolding, but treat it like training wheels. Once you know what each page needs, close over the session at the service layer and feed the view either a complete graph or a DTO. That stops templates from changing database behavior by accident. Mix fetch join for main paths, entity graphs for flexible reads, batch size for repeated to one, and projections for lists. Keep the code paths short and clear, and lean on transaction boundaries you can point at on a whiteboard. The news cycle will keep throwing us new toys every week, from Swift to new JS frameworks, but the database still runs at the speed of round trips. Your users will thank you if you make those trips count.

Make lazy loading a choice, not a surprise.

Software Architecture 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