How to store schema-less EAV (Entity-Attribute-Value) data using JSON and Hibernate

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!


One of my Twitter followers has recently asked me about a way of storing EAV (Entity-Attribute-Value) data using JPA and Hibernate, and, because this is a very good question, I decided to turn into a blog post.

In this article, you are going to see how you can use JSON types to store schema-less EAV data.

Domain Model

Let’s assume we have an online book store, and books are modeled via the Book entity:

@Entity(name = "Book")
@Table(name = "book")
    typeClass = JsonType.class, 
    defaultForType = JsonNode.class
public class Book {

    private Long id;

    private String isbn;

    @Column(columnDefinition = "jsonb")
    private JsonNode properties;

    //Getters and setters omitted for brevity

The Book has a unique isbn attribute, hence it’s mapped as a @NaturalId.

For more details about the @NaturalId annotation, check out this article.

The properties attribute is of type JsonNode which contains the EAV data structure.

Because Hibernate does not support JSON types natively, we need a JsonType to handle the Jackson-specific JsonNode object type.

Hibernate Types

The first thing you need to do is to set up the following Maven dependency in your project pom.xml configuration file:


That’s it!

Testing time

When persisting the following Book entity:

Book book = new Book();
book.setIsbn( "978-9730228236" );
        "{" +
        "   \"title\": \"High-Performance Java Persistence\"," +
        "   \"author\": \"Vlad Mihalcea\"," +
        "   \"publisher\": \"Amazon\"," +
        "   \"price\": 44.99" +

entityManager.persist( book );

Hibernate generates the proper SQL INSERT statement:

    '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',  

We can also fetch the Book entity and even modify it, and Hibernate will take care of all the persistence logic:

Session session = entityManager.unwrap( Session.class );

Book book = session
    .bySimpleNaturalId( Book.class )
    .load( "978-9730228236" ); "Book details: {}", book.getProperties() );

        "{" +
        "   \"title\": \"High-Performance Java Persistence\"," +
        "   \"author\": \"Vlad Mihalcea\"," +
        "   \"publisher\": \"Amazon\"," +
        "   \"price\": 44.99," +
        "   \"url\": \"\"" +

When running the test case above, Hibernate generates the following SQL statements:

SELECT AS id1_0_
FROM    book b
WHERE   b.isbn = '978-9730228236'

SELECT AS id1_0_0_ ,
        b.isbn AS isbn2_0_0_ , AS properti3_0_0_
FROM    book b

-- Book details: {"price":44.99,"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon"}

    properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":""}' 
    id = 1

Cool, right?

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


Hibernate types are great! Even if Hibernate comes with a great variety of types, even Java 1.8 Date/Time, you can easily map custom types like ARRAY or JSON.

For JSON, not only that you can map JSON structures as Java objects as we saw in a previous article, but you benefit from schema-less structures as well.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published.

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