The best way to lazy load entity attributes using JPA and Hibernate

(Last Updated On: January 22, 2019)
Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!


When fetching an entity, all attributes are going to be loaded as well. This is because every entity attribute is implicitly marked with the @Basic annotation whose default fetch policy is FetchType.EAGER.

However, the attribute fetch strategy can be set to FetchType.LAZY, in which case the entity attribute is loaded with a secondary select statement upon being accessed for the first time.

@Basic(fetch = FetchType.LAZY)

This configuration alone is not sufficient because Hibernate requires bytecode instrumentation to intercept the attribute access request and issue the secondary select statement on demand.

Bytecode enhancement

When using the Maven bytecode enhancement plugin, the enableLazyInitialization configuration property must be set to true as illustrated in the following example:


With this configuration in place, all JPA entity classes are going to be instrumented with lazy attribute fetching. This process takes place at build time, right after entity classes are compiled from their associated source files.

The attribute lazy fetching mechanism is very useful when dealing with column types that store large amounts of data (e.g. BLOB, CLOB, VARBINARY). This way, the entity can be fetched without automatically loading data from the underlying large column types, therefore improving performance.

To demonstrate how attribute lazy fetching works, the following example is going to use an Attachment entity which can store any media type (e.g. PNG, PDF, MPEG).

@Entity @Table(name = "attachment")
public class Attachment {

    private Long id;

    private String name;

    @Column(name = "media_type")
    private MediaType mediaType;

    @Basic(fetch = FetchType.LAZY)
    private byte[] content;

    //Getters and setters omitted for brevity

Properties such as the entity identifier, the name or the media type are to be fetched eagerly on every entity load. On the other hand, the media file content should be fetched lazily, only when being accessed by the application code.

After the Attachment entity is instrumented, the class bytecode is changed as follows:

private transient PersistentAttributeInterceptor 

public byte[] getContent() {
    return $$_hibernate_read_content();

public byte[] $$_hibernate_read_content() {
    if ($$_hibernate_attributeInterceptor != null) {
        this.content = ((byte[]) 
                this, "content", this.content));
    return this.content;

The content attribute fetching is done by the PersistentAttributeInterceptor object reference, therefore providing a way to load the underlying BLOB column only when the getter is called for the first time.

attachment table

When executing the following test case:

Attachment book = entityManager.find(
    Attachment.class, bookId);

LOGGER.debug("Fetched book: {}", book.getName());


Hibernate generates the following SQL queries:

SELECT AS id1_0_0_,
       a.media_type AS media_ty3_0_0_, AS name4_0_0_
FROM   attachment a

-- Fetched book: High-Performance Java Persistence

SELECT a.content AS content2_0_
FROM   attachment a

Because it is marked with the FetchType.LAZY annotation and lazy fetching bytecode enhancement is enabled, the content column is not fetched along with all the other columns that initialize the Attachment entity. Only when the data access layer tries to access the content property, Hibernate issues a secondary select to load this attribute as well.

Just like FetchType.LAZY associations, this technique is prone to N+1 query problems, so caution is advised. One slight disadvantage of the bytecode enhancement mechanism is that all entity properties, not just the ones marked with the FetchType.LAZY annotation, are going to be transformed, as previously illustrated.

Fetching subentities

Another approach to avoid loading table columns that are rather large is to map multiple subentities to the same database table.

Attachment Summary

Both the Attachment entity and the AttachmentSummary subentity inherit all common attributes from a BaseAttachment superclass.

public class BaseAttachment {

    private Long id;

    private String name;

    @Column(name = "media_type")
    private MediaType mediaType;

    //Getters and setters omitted for brevity

While AttachmentSummary extends BaseAttachment without declaring any new attribute:

@Entity @Table(name = "attachment")
public class AttachmentSummary 
    extends BaseAttachment {}

The Attachment entity inherits all the base attributes from the BaseAttachment superclass and maps the content column as well.

@Entity @Table(name = "attachment")
public class Attachment 
    extends BaseAttachment {

    private byte[] content;

    //Getters and setters omitted for brevity

When fetching the AttachmentSummary subentity:

AttachmentSummary bookSummary = entityManager.find(
    AttachmentSummary.class, bookId);

The generated SQL statement is not going to fetch the content column:

SELECT as id1_0_0_, 
       a.media_type as media_ty2_0_0_, as name3_0_0_ 
FROM attachment a 

However, when fetching the Attachment entity:

Attachment book = entityManager.find(
    Attachment.class, bookId);

Hibernate is going to fetch all columns from the underlying database table:

SELECT as id1_0_0_, 
       a.media_type as media_ty2_0_0_, as name3_0_0_, 
       a.content as content4_0_0_ 
FROM attachment a 

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.


To lazy fetch entity attributes, you can either use bytecode enhancement or subentities. Although bytecode instrumentation allows you to use only one entity per table, subentities are more flexible and can even deliver better performance since they don’t involve an interceptor call whenever reading an entity attribute.

When it comes to reading data, subentities are very similar to DTO projections. However, unlike DTO projections, subentities can track state changes and propagate them to the database.

Download free ebook sample

Newsletter logo
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence,
  • a 10% discount coupon for my book.

19 Comments on “The best way to lazy load entity attributes using JPA and Hibernate

  1. Nice article, thanks for that!

    What I miss is a configuration option to limit this feature on particular entities only.

    Hopefully this will become an option.

    • You already have this feature. Unless you explicitly use @Basic(lazy = true, all entities attributes are fetched eagerly. And even if you set that annotation, you still need to activate bytecode enhancement.

  2. Hi Vlad,
    Is there any way of delaying the actual hibernate mapping to run time (especially when the data source depends on the tenant id) ?

    The workarounds would be either just mapping to one of the tenants (security breach) or to a default empty db (ugly as night).

    Thank you,
    Bogdan I.

    • You cannot delay the mapping process during bootstrap. But you can delay to bootstrap altogether. However, I don’t really understand your use. Why do you want to delay the mapping?

      • Hi Vlad,
        Thank you for your quick reply: I need it because I’m multi-tenant. And during the app start I don’t have yet a tenant id for which bounded ds to create the EMF to.

      • If you don’t have the tenantId, you need to postpone the bootstrap until you know the DB connection URL to connect to.

      • How can I postpone the bootstrap ?
        Trouble is that I have spring boot and spring data on top :(.

      • That’s easy. Just use a separate bootstrapper application who starts the Spring Boot application only after it retrieves the tenant identifier.

    • In IDEA, you write the source code while the bytecode enhancement mechanism is for the runtime part. Even in debug mode, it won’t be a a problem. So, just build the project with Maven and you’ll be fine.

      • Well, yes and no.

        I’m using the enhancer to allow lazy-loading of inverse-side toOne relations. Which means when the enhancer is not working, hibernate fallbacks to classic proxies and because it doesn’t know whether to put in the property a lazy-proxy-object or null it executes extra query.

        This ensures the application works correctly even when enhancer is not used, but it also changes the behaviour a bit – executing queries when it shouldn’t be executing any.

        We have a custom listener for devs, that makes sure no lazy-loading occurs and when there is a query in unexpected place in wrong application layer. Thanks to this listener, now when I run tests from IDEA, it glows with WARN’s that tell me about unexpected queries, but when the tests run in CI from maven, everything is as expected.

        I want it to not only work correctly but also consistently no matter the environment. I’m unable to optimize the app, if it behaves differently in dev and prod 😦

      • I think you can tell Idea to build the project and run the tests through Maven so that you get the same outcome. If you can’t do it, then you should contact Jetbrains and ask them how that can be done.

  3. Hello Vlad, how are you ?

    Vlad, I have a issue with this implementation.

    When I will save the entity after use getContent, the Hibernate retrieve

    org.hibernate.bytecode.instrumentation.spi.LazyPropertyInitializer$1 cannot be cast to [B

    Would you help me please ?


    • All the examples are on GitHub and work like a charm. Just set up the repository and you’ll find the answer to your question.

      • Very thank’s Vlad.

        I will search in the codes.


      • Check out this video which shows you how to set up the GitHub repository. This is a sample of my video course, so if you like it, you will definitely love the course even more.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Want to run your data access layer at warp speed?