How to encrypt and decrypt JSON properties with JPA
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
Introduction
In this article, we are going to see how we can encrypt and decrypt JSON properties when using JPA and Hibernate.
While encrypting the entire column value is very straightforward, when it comes to JSON columns, we need to preserve the JSON object structure while only encrypting the JSON property values.
Domain Model
Let’s assume that our application defines a User entity that encapsulates all the user-sensitive information in a UserDetails object:

The User entity is mapped to the users database table, and the UserDetails object is saved in a JSON column:

Since the UserDetails contains user-specific information, we want to encrypt it prior to storing it in the database and decrypt it after loading it.
How to encrypt and decrypt JSON properties with JPA and Hibernate
The UserDetails is a simple POJO class that looks as follows:
public class UserDetails {
private String firstName;
private String lastName;
private String emailAddress;
//Getters and setters omitted for brevity
}
The User entity is going to be mapped as follows.
For Hibernate 6, the mapping will look as follows:
@Entity
@Table(name = "users")
@DynamicUpdate
public class User {
@Id
private Long id;
private String username;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private UserDetails details;
//Getters and setters omitted for brevity
@PrePersist
@PreUpdate
private void encryptFields() {
if (details != null) {
if (details.getFirstName() != null) {
details.setFirstName(
CryptoUtils.encrypt(details.getFirstName())
);
}
if (details.getLastName() != null) {
details.setLastName(
CryptoUtils.encrypt(details.getLastName())
);
}
if (details.getEmailAddress() != null) {
details.setEmailAddress(
CryptoUtils.encrypt(details.getEmailAddress())
);
}
}
}
@PostLoad
private void decryptFields() {
if (details != null) {
if (details.getFirstName() != null) {
details.setFirstName(
CryptoUtils.decrypt(details.getFirstName())
);
}
if (details.getLastName() != null) {
details.setLastName(
CryptoUtils.decrypt(details.getLastName())
);
}
if (details.getEmailAddress() != null) {
details.setEmailAddress(
CryptoUtils.decrypt(details.getEmailAddress())
);
}
}
}
}
And for Hibernate 5, like this:
@Entity
@Table(name = "users")
@DynamicUpdate
@TypeDef(typeClass = JsonType.class, defaultForType = UserDetails.class)
public class User {
@Id
private Long id;
private String username;
@Column(columnDefinition = "json")
private UserDetails details;
//Getters and setters omitted for brevity
@PrePersist
@PreUpdate
private void encryptFields() {
if (details != null) {
if (details.getFirstName() != null) {
details.setFirstName(
CryptoUtils.encrypt(details.getFirstName())
);
}
if (details.getLastName() != null) {
details.setLastName(
CryptoUtils.encrypt(details.getLastName())
);
}
if (details.getEmailAddress() != null) {
details.setEmailAddress(
CryptoUtils.encrypt(details.getEmailAddress())
);
}
}
}
@PostLoad
private void decryptFields() {
if (details != null) {
if (details.getFirstName() != null) {
details.setFirstName(
CryptoUtils.decrypt(details.getFirstName())
);
}
if (details.getLastName() != null) {
details.setLastName(
CryptoUtils.decrypt(details.getLastName())
);
}
if (details.getEmailAddress() != null) {
details.setEmailAddress(
CryptoUtils.decrypt(details.getEmailAddress())
);
}
}
}
}
The @DynamicUpdate annotation is used because we want Hibernate to include only the modified columns when generating an UPDATE statement. For more details about the @DynamicUpdate annotation, check out this article.
The @TypeDef annotation is used for Hibernate 5 only, and it instructs Hibernate to use the JsonType provided by the Hypersistence Utils project when persisting and fetching entity attributes of the UserDetails type.
The encryptFields method is annotated with the JPA @PrePersist and @PreUpdate annotations, so the JPA provider will call this method prior to persisting or updating the entity. Therefore, we are going to use the encryptFields method to encrypt the attribute values of the UserDetails object.
The decryptFields method is annotated with the JPA @PostLoad annotation, so the JPA provider is going to call this method after fetching the entity. Therefore, we are going to use the decryptFields method to decrypt the attribute values of the UserDetails object.
The CryptoUtils class is located in my High-Performance Java Persistence GitHub repository, and for brevity sake, it’s been omitted.
For more details about the
@PrePersist,@PreUpdate, and@PostLoadJPA annotations, check out this article as well.
Testing time
When persisting the following User entity:
entityManager.persist(
new User()
.setId(1L)
.setUsername("vladmihalcea")
.setDetails(
new UserDetails()
.setFirstName("Vlad")
.setLastName("Mihalcea")
.setEmailAddress("info@vladmihalcea.com")
)
);
Hibernate generates the following SQL INSERT statement:
INSERT INTO users (
details,
username,
id
)
VALUES (
{
"firstName":"3Pj42hikNEQ5Z3gQplc2AQ==",
"lastName":"xTC5Ef4MFEhU4/K7a7+WHw==",
"emailAddress":"6IuTqZ4e9N80vvutCztnddjNpvuNe/BGn1MrAck3sic="
},
vladmihalcea,
1
)
Notice that only the JSON property values have been encrypted. The details column value is still a valid JSON object. If we encrypted the entre JSON column value, the DB would throw a constraint violation since the provided encrypted string value would not be a valid JSON object.
When loading the User entity, we can see that the UserDetails properties are properly decrypted:
User user = entityManager.find(User.class,1L);
UserDetails userDetails = user.getDetails();
assertEquals("Vlad", userDetails.getFirstName());
assertEquals("Mihalcea", userDetails.getLastName());
assertEquals("info@vladmihalcea.com", userDetails.getEmailAddress());
When updating a UserDetails property:
User user = entityManager.find(User.class, 1L);
user.getDetails().setEmailAddress("noreply@vladmihalcea.com");
We can see that the UPDATE statement will contain the new details column value with the emailAddress property value containing the new encrypted email value:
UPDATE users
SET
details = {
"firstName":"3Pj42hikNEQ5Z3gQplc2AQ==",
"lastName":"xTC5Ef4MFEhU4/K7a7+WHw==",
"emailAddress":"JBBe6+rKdNjWdp47rFOy29l1X6vnY3L3R5OhCZGaF74="
}
WHERE
id = 1
Awesome, right?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
JPA makes it very easy to encrypt and decrypt JSON properties due to its entity listener methods. And, if you’re using Hibernate, you can benefit from the Hypersistence Utils project to map JSON columns, no matter if you’re using Oracle, SQL Server, PostgreSQL or MySQL.






