Creating Java JPA entities with a JSON field

by Liger Learn
1. Introduction
In this tutorial we will explore how you can persist a JSON field in your JPA entity.
We use a number of technologies:
- Spring Data JPA
- Spring Data REST
- Hibernate ORM
- Jackson
- PostgreSQL
As always, the code used in the article is available on GitHub here.
This article is focused on storing a JSON field in a JPA entity. If you would like to see how to store an XML field – check out this article here.
2. Our Goal
Our goal is to be able to perform create, read, update and delete (CRUD) operations on a JPA entity that contains a field which uses PostgreSQL’s native JSON/JSONB column type. To illustrate how to achieve this we will:
- add the correct dependencies to our project.
- create a simple domain model with a JSON field in an entity.
- create a simple
@RepositoryRestResource
which exposes endpoints illustrating CRUD operations on the entity.
Let’s get started!
3. Adding the correct project dependencies
In order to define JSON fields within an @Entity
we need to have a specific additional dependency: the hibernate types project. For maven, you can add the following to your pom.xml
file:
1 2 3 4 5 |
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>2.9.11</version> </dependency> |
And for gradle you can add the following to your build.gradle
file:
1 |
implementation 'com.vladmihalcea:hibernate-types-52:2.9.11' |
As of Jun 2020 the latest version of the project is v.2.9.11 so that it what we will use here. You can check all of the other dependencies required for the project here.
4. Our domain model
We use an extremely simple domain model so we can focus on the addition of the JSON field:
1 2 3 4 5 6 7 |
CREATE TABLE IF NOT EXISTS person ( id bigserial PRIMARY KEY, first_name text, surname text, address jsonb ); |
We have a person which has an auto-generated ID, first_name, surname and address. The address is stored using PostgreSQL’s native JSONB type.
4.1 An Address
(JSON type)
An address is a carrier of data (a record) which we define as such:
1 2 3 4 5 6 7 8 9 |
@Data @NoArgsConstructor @AllArgsConstructor public class Address implements Serializable { private final static long serialVersionUID = 7702L; private int doorNumber; private String roadName; private String postalCode; } |
We use a number of convenience lombok annotations:
@Data
will make sure, among other things, that there are getters and setter for each field.@NoArgsConstructor
will make sure that a constructor which takes no arguments (public Address()
) exists.@AllArgsConstructor
will make a constructor with all of the three arguments (doorNumber
,roadName
,postalCode
) for convenience.
We use these annotations to have the code required by the JSON serialiser (which defaults to Jackson) generated for us instead of manually writing it.
4.2 A Person
(JPA entity)
We define as Person as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Data @Entity @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) // See (1) public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private long id; @Column(name = "first_name") private String firstName; @Column(name = "surname") private String surname; @Type(type = "jsonb") // See (2) @Column(name = "address", columnDefinition = "jsonb") private Address address; } |
- The
@TypeDef
annotation is used to create an alias for theJsonBinaryType
which we can refer to asjsonb
within@Type
definitions in the class (which we do in (2)).- In this case we have placed the
@TypeDef
annotation on the class itself. - It is useful to know that it is also possible to place it on a
package-info.java
file so that all classes within a package can refer to the@TypeDef
without having to redefine it for each class.
- In this case we have placed the
- The
@Type
annotation is used to refer to thejsonb
@TypeDef
that we defined in 1.- This allows hibernate to know how to serialise/deserialise the field.
The rest of the class is self explanatory – it contains the ID of the Person as well as their first name and surname. The ID is generated using the IDENTITY strategy which means the database will provide the generated ID when needed.
5. CRUD operations
Let’s now illustrate how we can perform CRUD operations on the entity and have the Address
persisted as JSON in the PostgreSQL database. We do this by utilising the Spring Data REST project’s functionality that allows us to easily expose a typical REST API for the Person
resource. From the documentation:
Spring Data REST builds on top of Spring Data repositories, analyzes your application’s domain model and exposes hypermedia-driven HTTP resources for aggregates contained in the model.
5.1 The @RepositoryRestResource
Let’s create a @RepositoryRestResource
for our Person
:
1 2 |
@RepositoryRestResource(path = "people", itemResourceRel = "person", collectionResourceRel = "people") public interface PersonRestRepository extends JpaRepository<Person, Long> {} |
Here we are stating our that:
- the path at
/people
is where the endpoints should be generated. - when we have a related link to a single item of this resource (
itemResourceRel
) we should use the wordperson
(in other words for the singular case useperson
). - when we have a related link to a collection of these resources (
collectionResourceRel
) we should use the wordpeople
(in other words for the plural case usepeople
).
For our simple use case we can really just ignore the itemResourceRel
and collectionResourceRel
but the explanation above is there for those who want to understand why we defined them this way.
6. Testing the @RepositoryRestResource
To run the application we need to run the bootRun
gradle task. With our @RepositoryRestResource
being defined we can now go ahead and test the expected endpoints:
- Creating a person resource:
- POST
/people
- POST
- Reading a person resource:
- GET
/people/{id}
- GET
- Updating a person resource:
- PUT
/people/{id}
- PUT
- Deleting a person resource:
- DELETE
/people/{id}
- DELETE
6.1 [POST] (Crud) Creating a person resource
Let’s create a new person by issuing the command.
1 2 3 4 5 6 7 8 9 10 |
# Create a new person curl -X POST -H "Content-Type:application/json" -d '{ "firstName": "John", "surname": "Smith", "address": { "doorNumber": 2, "roadName": "London Road", "postalCode": "EC34AWE" } }' http://localhost:8080/people |
We receive a response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "firstName" : "John", "surname" : "Smith", "address" : { "doorNumber" : 2, "roadName" : "London Road", "postalCode" : "EC34AWE" }, "_links" : { "self" : { "href" : "http://localhost:8080/people/1" }, "person" : { "href" : "http://localhost:8080/people/1" } } } |
Indicating that the entity has been saved and is available at the endpoint http://localhost:8080/people/1
. Let’s make a GET request to read the data at this endpoint to make sure it has been persisted.
6.2 [GET] (cRud) Reading a person resource
In the previous subsection we created (using a POST request) a Person
with id=1. Let’s now try to get the data associated with the Person with id=1. Remember, the whole purpose of this article/tutorial is to illustrate how to store a JSON field using JPA. So let’s first read the data by running a SQL query to check how it is persisted in the database:
We can see that the database has stored the field as JSON just as we would want. Now let’s query for the data using a HTTP request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# Request curl http://localhost:8080/people/1 # Response { "firstName" : "John", "surname" : "Smith", "address" : { "doorNumber" : 2, "roadName" : "London Road", "postalCode" : "EC34AWE" }, "_links" : { "self" : { "href" : "http://localhost:8080/people/1" }, "person" : { "href" : "http://localhost:8080/people/1" } } } |
We receive the response as expected!
6.3 [PUT] (crUd) Updating a person resource
Let’s now update the address of the person and see how that works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# Request curl -X PUT -H "Content-Type:application/json" -d '{ "firstName": "John", "surname": "Smith", "address": { "doorNumber": 35, "roadName": "New York Road", "postalCode": "12345" } }' http://localhost:8080/people/1 # Response { "firstName" : "John", "surname" : "Smith", "address" : { "doorNumber" : 35, "roadName" : "New York Road", "postalCode" : "12345" }, "_links" : { "self" : { "href" : "http://localhost:8080/people/1" }, "person" : { "href" : "http://localhost:8080/people/1" } } } |
And the data has been updated in the database too:
The JSON was updated as we would expect!
6.4 [DELETE] (cruD) Deleting a person resource
Let’s now delete the Person:
1 2 3 4 5 6 |
# Delete (returns nothing which indicated success) curl -X DELETE http://localhost:8080/people/1 # No response when we try to get the person with the given ID. # Meaning it was deleted succesfully. curl http://localhost:8080/people/1 |
7. Conclusion
In this tutorial we explored how we can store a JSON field in a JPA entity backed by a PostgreSQL database which uses a table with the native jsonb
type. We created a @RepositoryRestResource
for a simple Person
resource that stored its address as JSON. We then illustrated how we could test CRUD operations on the entity and finally we saw how instances of the entity were persisted successfully as JSON in the database.
All of the source code for the project is available here.
Recommended Posts

Creating Java JPA entities with an XML field
May 25, 2020

Using Emacs to edit files within Docker containers
March 12, 2017

How to edit files within docker containers
March 11, 2017