How to map JSON collections using JPA and Hibernate

(Last Updated On: February 2, 2018)

Introduction

The open-source hibernate-types project allows you to map Java objects or Jackson JsonNode as JPA or Hibernate entity properties, and, thanks to our awesome contributors, we have added support for storing type-safe JSON collections.

In this article, you are going to see how to achieve this goal.

Maven dependency

First of all, you need to set up the following Maven dependency in your project pom.xml configuration file:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

If you’re using older versions of Hibernate, check out the hibernate-types GitHub repository for more info about the matching dependency for your current Hibernate version.

Domain Model

Let’s assume we have the following Location Java object type.

public class Location implements Serializable {

    private String country;

    private String city;

    //Getters and setters omitted for brevity

    @Override
    public String toString() {
        return "Location{" +
                "country='" + country + ''' +
                ", city='" + city + ''' +
                '}';
    }
}

And, one Event entity:

@Entity(name = "Event")
@Table(name = "event")
public class Event extends BaseEntity {

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private Location location;

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private List<Location> alternativeLocations = new ArrayList<Location>();

    //Getters and setters omitted for brevity
}

The BaseEntity defines some basic properties (e.g. @Id, @Version) and several custom Hibernate types, among which, we are interested in the JsonBinaryType one.

@TypeDefs({
    @TypeDef(name = "string-array", typeClass = StringArrayType.class),
    @TypeDef(name = "int-array", typeClass = IntArrayType.class),
    @TypeDef(name = "json", typeClass = JsonStringType.class),
    @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class),
    @TypeDef(name = "jsonb-node", typeClass = JsonNodeBinaryType.class),
    @TypeDef(name = "json-node", typeClass = JsonNodeStringType.class),
})
@MappedSuperclass
public class BaseEntity {

    @Id
    private Long id;

    @Version
    private Integer version;

    //Getters and setters omitted for brevity
}

For more details about using @MappedSuperclass, check out this article.

To store both the Location object or the List<Location> in a jsonb PostgreSQL column, we just need to annotate the location property with @Type(type = "jsonb").

That’s it!

Testing time

When saving the following Event entity:

Location cluj = new Location();
cluj.setCountry("Romania");
cluj.setCity("Cluj-Napoca");

Location newYork = new Location();
newYork.setCountry("US");
newYork.setCity("New-York");

Location london = new Location();
london.setCountry("UK");
london.setCity("London");

Event event = new Event();
event.setId(1L);
event.setLocation(cluj);
event.setAlternativeLocations(
    Arrays.asList(newYork, london)
);

entityManager.persist(event);

Hibernate will generate the following SQL INSERT statement:

INSERT INTO event (
    version, 
    alternativeLocations, 
    location, 
    id
) 
VALUES (
    0, 
    [
        {"country":"US","city":"New-York"},
        {"country":"UK","city":"London"}
    ], 
    {"country":"Romania","city":"Cluj-Napoca"}, 
    1
)

Also, when retrieving back the Event entity, both the location and thealternativeLocations` properties are properly fetched:

Event event = entityManager.find(Event.class, eventId);

assertEquals(
    "Cluj-Napoca", 
    event.getLocation().getCity()
);

assertEquals(2, event.getAlternativeLocations().size());

assertEquals(
    "New-York", 
    event.getAlternativeLocations().get(0).getCity()
);
assertEquals(
    "London", 
    event.getAlternativeLocations().get(1).getCity()
);

Cool, right?

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

Conclusion

The hibernate-types project supports more than JSON types. You can map PostgreSQL ARRAY types or PostgreSQL-specific Enums, nullable Character, or even provide your own immutable Hibernate custom Types.

Subscribe to our Newsletter

* indicates required
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. 
Get the most out of your persistence layer!

Advertisements

26 thoughts on “How to map JSON collections using JPA and Hibernate

  1. Hi Vlad, I’m experiencing the same problem of Joao and André when deploying on JBoss EAP 7, which ships the following Hibernate versions:

    5.1.10.Final-redhat-1
    5.0.1.Final-redhat-2
    ${version.org.hibernate}
    5.3.5.Final-redhat-2
    5.5.8.Final-redhat-1

    In my pom I just added the dependency:

            com.vladmihalcea
            hibernate-types-5
            2.2.3
    

    but at deployment time it complains about a missing class from hibernate-commons-annotations:

    Caused by: java.lang.NoClassDefFoundError: org/hibernate/annotations/common/reflection/XProperty
    at com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor.setParameterValues(JsonTypeDescriptor.java:58)
    at com.vladmihalcea.hibernate.type.json.JsonBinaryType.setParameterValues(JsonBinaryType.java:66)
    at org.hibernate.type.TypeFactory.injectParameters(TypeFactory.java:142)

    The class is indeed present in the jar provided by JBoss but it looks like the ModuleClassLoader is not able to find it.

    Can you give me some hint about how to overcome this problem?

    Thanks al ot

    1. I’m not familiar with JBoss and Wildfly modules, so you need to ask the question on the JBoss forum. They will surely know how to sort it out.

  2. Is it possible to store the JSon column value as “String”? I am getting error

    Schema-validation: wrong column type encountered in column [VALIDATION_ADDITIONAL_ATTRIBUTES] in table [FI_FILE_VALIDATION_T]; found [json (Types#OTHER)], but expecting [varchar(255) (Types#VARCHAR)]

    My Entity is like followoing

    @Entity
    @Table(name=”FI_FILE_VALIDATION_T”, schema=”XXX”)

    @TypeDefs({
    @TypeDef(name = “json”, typeClass = JsonStringType.class),
    @TypeDef(name = “jsonb”, typeClass = JsonBinaryType.class),
    @TypeDef(name = “jsonb-node”, typeClass = JsonNodeBinaryType.class),
    @TypeDef(name = “json-node”, typeClass = JsonNodeStringType.class),
    })
    public class FIFileValidation implements Serializable{


    @org.hibernate.annotations.Type(type=”jsonb”)
    @Column(name=”VALIDATION_ADDITIONAL_ATTRIBUTES”,columnDefinition=”jsonb”)
    private Map<String,String> validationAdditionalAttributes;

    ..

    Thanks
    Himanshu

    1. The json and jsob types are for storing data in JSON column types. If you need to store the JSON in a VARCHAR, just use a String entity property and it will work even without the hibernate-types project.

  3. Hi Vlad,

    I’m trying your library (hibernate-types) and faced with the issue. Can you please help.
    Project details:
    Spring 4.3.10, Hibernate 5.2.5, hibernate-types-52 2.2.3, connection pool – c3p0 0.9.5.2, jackson 2.3.2.
    Env:
    Jboss 7, MsSql, jdk8

    Java model:
    BaseEntity – copy from the article.

    @Entity
    @Table(name = “B_BOOK”)
    public class Book extends BaseEntity {
    private String title;
    private String author;

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private Details details;

    }

    public class Details implements Serializable {
    private String details;
    @Override
    public String toString() { return “Details{details=’” + details + “\’}”; }
    }

    With GET request I get properly created Book with Details.
    With POST request without Details object I get Book saved in DB.
    With POST request with Details object I get exception:
    com.microsoft.sqlserver.jdbc.SQLServerException: The conversion from UNKNOWN to OTHER is unsupported.

    What was done wrongly?

    Thanks in advance,
    Evgeniy

    1. I never tested it with SQL Server. You need to see if String or Binary work better, and supply a Pull Request with a new type if the existing ones don’t work. Looking forward to your contribution.

  4. Mallikarjuna Reddy,

    did you solve this error? I got the same error as yours for jsonb, but it works for json type when saving and receiving

  5. Hey Vlad. Quick question, would the implementation of HashMap/Map work with jsonb as shown in the code below?

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private  Map<String, String> stuff;
    
    1. I have never tested it with a Map, but you can give it a try and see if it works. If it does not, send me a Pull Request with the enhancement. Thanks.

  6. Really really cool Vlad, congrats!!!

    I’m trying to do the same but I’m getting :
    IllegalArgumentException: Not a managed type

    @Entity
    public class Sample extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Type(type = “jsonb”)
    @Column(columnDefinition = “jsonb”)
    private List exams = new ArrayList();

    }

    Same as you did. There is any Git repo where I could see more samples? 🙂
    I shall really appreciate it 🙂 Thanks.

  7. I’m trying to use your library in wildfly 11.0.0.Final that uses hibernate 5.1.10.Final., but i’m facing some problems: when i bootstrap the application the result is
    java.lang.ClassNotFoundException: org.hibernate.annotations.common.reflection.XProperty.

    Can you give me some tips about my problem?

    1. I have never used Wildfly. However, you just need to make sure you have the hibernate-commons-annotations artifact in your application class-path.

      1. I’m having the same problem.
        And having hibernate-commons-annotations on classpath resulted in
        java.lang.ClassCastException: org.hibernate.annotations.common.reflection.java.JavaXProperty cannot be cast to org.hibernate.annotations.common.reflection.XProperty

    2. I solved it! Wildfly(or jboss) provide hibernate. And I don’t know why, he search hibernate in the deployed ear, where doesn’t can be.

      I changed my ear’s pom in the “maven-ear-plugin.configuration.archive” section.

      org.hibernate.commons-annotations

      H.

      1. Great! so did you included all the Hibernate libraries in the EAR ? or just the org.hibernate.commons-annotations jar ?

      2. No! Just wildfly searched it there.

        I changed my ear’s pom in the “maven-ear-plugin.configuration.archive” section.

        org.hibernate.commons-annotations

  8. Hi vlad, thanks for your great article.
    I encountered a problem here, I use hibernate and liquibase:

    2018-04-15 15:25:56.017 ERROR 21184 — [ main] o.h.metamodel.internal.MetadataContext : HHH015007: Illegal argument on static metamodel field injection : com.james.app.domain.Ability_#abilities; expected type : org.hibernate.metamodel.internal.SingularAttributeImpl; encountered type : javax.persistence.metamodel.ListAttribute

    my domain class abilities field is as below:
    @Type(type = “json”)
    @Column(columnDefinition = “json”)
    private List abilities = new ArrayList();

    Could you please help? very very appreicated.

      1. I tried 5.2.17-SNAPSHOT, I cannot see the original exception, however a new exception: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing column [abilities] in table [ability], but the table has abilities indeed, don’t konw why.

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.