eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Partner – LambdaTest – NPI EA (cat=Testing)
announcement - icon

Regression testing is an important step in the release process, to ensure that new code doesn't break the existing functionality. As the codebase evolves, we want to run these tests frequently to help catch any issues early on.

The best way to ensure these tests run frequently on an automated basis is, of course, to include them in the CI/CD pipeline. This way, the regression tests will execute automatically whenever we commit code to the repository.

In this tutorial, we'll see how to create regression tests using Selenium, and then include them in our pipeline using GitHub Actions:, to be run on the LambdaTest cloud grid:

>> How to Run Selenium Regression Tests With GitHub Actions

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

This tutorial provides a comprehensive understanding of storing JSON data in a PostgreSQL JSONB column.

Using JPA, we’ll quickly review how we can handle a JSON value stored as a variable character (VARCHAR) database column. After that, we’ll compare the differences between the VARCHAR type and the JSONB type to understand the additional features of JSONB. Finally, we’ll address the mapping JSONB type in JPA.

2. VARCHAR Mapping

In this section, we’ll explore converting a JSON value in VARCHAR type to a custom Java POJO. For this purpose, we’ll useAttributeConverter to easily convert from an entity attribute value in Java data type to its corresponding value in the database column.

2.1. Maven Dependency

To create an AttributeConverter, we have to include the latest Spring Data JPA dependency in the pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>3.3.2</version>
</dependency>

2.2. Table Definition

Let’s illustrate this concept with a simple example using the following database table definition:

CREATE TABLE student (
    student_id VARCHAR(8) PRIMARY KEY,
    admit_year VARCHAR(4),
    address VARCHAR(500)
);

The student table has three fields, and we’re expecting the address column to store JSON values with the following structure:

{
  "postCode": "TW9 2SF",
  "city": "London"
}

2.3. Entity Class

To handle this, we’ll create a corresponding POJO class to represent the address data in Java:

public class Address {
    private String postCode;

    private String city;

    // constructor, getters and setters
}

Next, we’ll create an entity class, StudentEntity, and map it to the student table we created earlier:

@Entity
@Table(name = "student")
public class StudentEntity {
    @Id
    @Column(name = "student_id", length = 8)
    private String id;

    @Column(name = "admit_year", length = 4)
    private String admitYear;

    @Convert(converter = AddressAttributeConverter.class)
    @Column(name = "address", length = 500)
    private Address address;

    // constructor, getters and setters
}

We’ll annotate the address field with @Convert and apply AddressAttributeConverter to convert the Address instance into its JSON representation.

2.4. AttributeConverter

Earlier, we mapped the address field in the entity class as a VARCHAR type in the database. However, JPA cannot automatically convert the custom Java type and the VARCHAR type. So, AttributeConverter comes in to bridge this gap by providing a mechanism to handle the conversion process.

We use AttributeConverter to persist a custom Java data type to a database column. It’s mandatory to define two conversion methods for every AttributeConverter implementation. One, named convertToDatabaseColumn(), converts the Java data type to its corresponding database data type, while the other, named convertToEntityAttribute(), converts the database data type to the Java data type:

@Converter
public class AddressAttributeConverter implements AttributeConverter<Address, String> {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(Address address) {
        try {
            return objectMapper.writeValueAsString(address);
        } catch (JsonProcessingException jpe) {
            log.warn("Cannot convert Address into JSON");
            return null;
        }
    }

    @Override
    public Address convertToEntityAttribute(String value) {
        try {
            return objectMapper.readValue(value, Address.class);
        } catch (JsonProcessingException e) {
            log.warn("Cannot convert JSON into Address");
            return null;
        }
    }
}

2.5. Test Case

Now, we can verify that a Student row is persisted correctly along with its Address:

@Test
void whenSaveAnStudentEntityAndFindById_thenTheRecordPresentsInDb() {
    String studentId = "23876213";
    String postCode = "KT5 8LJ";

    Address address = new Address(postCode, "London");
    StudentEntity studentEntity = StudentEntity.builder()
      .id(studentId)
      .admitYear("2023")
      .address(address)
      .build();

    StudentEntity savedStudentEntity = studentRepository.save(studentEntity);

    Optional<StudentEntity> studentEntityOptional = studentRepository.findById(studentId);
    assertThat(studentEntityOptional.isPresent()).isTrue();

    studentEntity = studentEntityOptional.get();
    assertThat(studentEntity.getId()).isEqualTo(studentId);
    assertThat(studentEntity.getAddress().getPostCode()).isEqualTo(postCode);
}

Moreover, we can check the logs to see what JPA is doing when we’re inserting new data:

Hibernate: 
    insert 
    into
        "public"
        ."student_str" ("address", "admit_year", "student_id") 
    values
        (?, ?, ?)
binding parameter [1] as [VARCHAR] - [{"postCode":"KT6 7BB","city":"London"}]
binding parameter [2] as [VARCHAR] - [2023]
binding parameter [3] as [VARCHAR] - [23876371]

We can see that the first parameter has been converted successfully from our Address instance by the AddressAttributeConverter and binds as a VARCHAR type.

We learned how to persist JSON data by converting the JSON data to VARCHAR and vice-versa. Next, let’s check other solutions to handle JSON data.

3. JSONB Over VARCHAR

In PostgreSQL, we can set the type of a column as JSONB to save JSON data:

CREATE TABLE student (
    student_id VARCHAR(8) PRIMARY KEY,
    admit_year VARCHAR(4),
    address jsonb
);

Here, we’re defining the address column as JSONB. This is a different data type than the previously used VARCHAR type, and it is important to learn why we have this data type in PostgreSQL. JSONB is a designated data type for processing JSON data in PostgreSQL.

Moreover, columns using JSONB type store data in a decomposed binary format, which has a bit of overhead when storing JSON due to the additional conversion.

Also, JSONB provides additional features compared to VARCHAR. Therefore, JSONB is a more favorable choice for storing JSON data in PostgreSQL.

3.1. Validation

The JSONB type enforces data validation to make sure the column value is a valid JSON. So, attempting to insert or update a column of type JSONB with invalid JSON values fails.

To demonstrate this, we can try to insert a SQL query with an invalid JSON value for the address column where, for example, a double quote is missing at the end of the city attribute:

INSERT INTO student(student_id, admit_year, address) 
VALUES ('23134572', '2022', '{"postCode": "E4 8ST, "city":"London}');

Running this query in PostgreSQL results throws a validation error indicating the JSON stating that we have an invalid JSON:

SQL Error: ERROR: invalid input syntax for type json
  Detail: Token "city" is invalid.
  Position: 83
  Where: JSON data, line 1: {"postCode": "E4 8ST, "city...

3.2. Querying

PostgreSQL supports querying using JSON columns in SQL queries. JPA supports using native queries to search for records in the database. In Spring Data, we can define a custom query method that finds a list of Student:

@Repository
public interface StudentRepository extends CrudRepository<StudentEntity, String> {
    @Query(value = "SELECT * FROM student WHERE address->>'postCode' = :postCode", nativeQuery = true)
    List<StudentEntity> findByAddressPostCode(@Param("postCode") String postCode);
}

This query is a native SQL query that selects all Student instances in the database where the address JSON attribute postCode equals the provided parameter.

3.3. Indexing

JSONB supports JSON data indexing. This gives JSONB a significant advantage when we have to query the data by keys or attributes in the JSON column.

Various types of indexes can be applied to a JSON column, including GIN, HASH, and BTREE. GIN is suitable for indexing complex data structures, including arrays and JSON. HASH is important when we only need to consider the equality operator =. BTREE allows efficient queries when we deal with range operators such as < and >=.

For example, we could create the following index if we always need to retrieve data according to the postCode attribute in the address column:

CREATE INDEX idx_postcode ON student USING HASH((address->'postCode'));

4. JSONB Mapping

We cannot apply the same AttributeConverter when the databases column is defined as JSONB. Otherwise, our application throws the following error upon start-up if we attempt to:

org.postgresql.util.PSQLException: ERROR: column "address" is of type jsonb but the expression is of type character varying

Even if we change the AttributeConverter class definition to use Object as the converted column value instead of String we’ll still get an error:

@Converter 
public class AddressAttributeConverter implements AttributeConverter<Address, Object> {
    // 2 conversion methods implementation
}

Our application complains about the unsupported type:

org.postgresql.util.PSQLException: Unsupported Types value: 1,943,105,171

Therefore, we can confidently say that JPA doesn’t support JSONB type natively. However, our underlying JPA implementation, Hibernate, does support JSON custom types that allow us to map a complex type to a Java class.

4.1. Maven Dependency

In short, we need a custom type for JSONB conversion. Fortunately, we can rely on an existing library named Hypersistence Utilities.

Hypersistence Utilities is a general-purpose utility library for Hibernate. One of its features is the definition of  JSON column type mapping for different databases such as PostgreSQL and Oracle. Thus, we can include this additional dependency in the pom.xml:

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-60</artifactId>
    <version>3.9.0</version>
</dependency>

4.2. Updated Entity Class

Hypersistence Utilities defines different custom types that are database-dependent. In PostgreSQL, we’ll use the JsonBinaryType class for the JSONB column type. In our entity class, we define the custom type using Hibernate’s @TypeDef annotation and then apply the defined type to the address field via @Type:

@Entity
@Table(name = "student")
public class StudentEntity {
    @Id
    @Column(name = "student_id", length = 8)
    private String id;

    @Column(name = "admit_year", length = 4)
    private String admitYear;

    @Type(JsonBinaryType.class)
    @JdbcTypeCode(SqlTypes.JSON)
    @Column(name = "address", columnDefinition = "jsonb")
    private Address address;

    // getters and setters
}

For this case of using @Type, we don’t need to apply the AttributeConverter to the address field anymore. The custom type from Hypersistence Utilities handles the conversion task for us, making our code more neat.

Note: Keep in mind that @Type annotation is deprecated in Hibernate 6.

4.3. Test Case

After all these changes, let’s re-run the Student persistence test case again:

Hibernate: 
    insert 
    into
        "public"
        ."student" ("address", "admit_year", "student_id") 
    values
        (?, ?, ?)
binding parameter [1] as [OTHER] - [Address(postCode=KT6 7BB, city=London)]
binding parameter [2] as [VARCHAR] - [2023]
binding parameter [3] as [VARCHAR] - [23876371]

We’ll see that JPA triggers the same insert SQL as before, except the first parameter is binding as OTHER instead of VARCHAR. This indicates that Hibernate binds the parameter as a JSONB type this time.

5. Conclusion

In this tutorial, we learned how to manage JSON data in PostgreSQL using Spring Boot and JPA. First, we addressed the mapping of JSON value to VARCHAR type and JSONB type using a custom converter. Then, we learned about the importance of using JSONB to enforce JSON validation and query and index JSON values easily. Finally, we implemented a custom type conversion of JSONB columns using the Hypersistence library.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LSD – NPI (cat=JPA)
announcement - icon

Get started with Spring Data JPA through the reference Learn Spring Data JPA:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)
3 Comments
Oldest
Newest
Inline Feedbacks
View all comments