Working with PostgreSQL’s advanced JSON and JSONB column types has become increasingly common in modern software development. These flexible data structures enable applications to implement semi-structured or schema-light storage patterns without fully committing to a NoSQL database. Meanwhile, Java applications often need a clean, type-safe, and low-boilerplate way to map these JSON fields to domain objects. This is where asentinel-orm comes in.
asentinel-orm is a lightweight Object-Relational Mapping (ORM) tool designed to simplify the interaction between Java objects and relational databases without the complexities of large frameworks such as JPA or Hibernate. It includes native support for PostgreSQL JSON and JSONB data types, allowing developers to map JSON fields directly to Java classes or generic structures with minimal friction.
This article provides a step-by-step guide on how to map PostgreSQL JSON and JSONB columns in Java using asentinel-orm, complete with coding examples, best practices, and implementation strategies.
Understanding PostgreSQL JSON and JSONB
Before diving into Java mapping specifics, it is important to understand the difference between PostgreSQL’s two JSON-related column types:
JSON
-
Stores the data as plain text.
-
Preserves the exact formatting, including whitespace.
-
Slower for querying because it requires parsing on each read.
JSONB
-
Stores the JSON in a binary decomposed format.
-
Performs faster query operations, indexing, and search scans.
-
Does not store formatting.
Most production systems lean toward using JSONB unless the exact text representation must be preserved.
When using Java as the application language, JSON and JSONB values typically need to be deserialized into POJOs, Maps, or Lists. This is precisely what asentinel-orm facilitates.
How asentinel-orm Approaches JSON Mapping
asentinel-orm allows developers to define mappers that handle conversions between PostgreSQL types and Java objects. For JSON/JSONB fields, it uses a customizable JsonMapper interface internally, which can be implemented using JSON libraries including Jackson, Gson, or others.
Most commonly, you will use Jackson because of its strong support for working with nested objects.
The ORM also supports:
-
Mapping JSON fields to POJOs (Java classes)
-
Mapping JSON fields to
Map<String, Object> -
Mapping JSON fields to
List<Object> -
Saving Java objects directly to JSON columns
-
Automatic conversion during SELECT and INSERT/UPDATE operations
Let’s walk through the full setup.
Setting Up Your Dependencies
To begin, you must include asentinel-orm and your preferred JSON library in your project.
Maven dependency example
<dependency>
<groupId>com.asentinel.orm</groupId>
<artifactId>asentinel-orm</artifactId>
<version>1.0.0</version>
</dependency><dependency><groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
Make sure your PostgreSQL driver is also included:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.0</version>
</dependency>
Now that the groundwork is laid, let’s examine how your database schema looks.
PostgreSQL Table with JSONB Column
Here’s a simple table where one field stores complex JSON data:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
profile JSONB NOT NULL
);
The profile column may store object data such as:
{
"firstName": "Alice",
"lastName": "Rivers",
"preferences": {
"theme": "dark",
"notifications": true
}
}
We want this JSON stored in profile to map to a Java class automatically.
Creating a Java POJO for the JSON Field
Let’s define a corresponding Java class:
public class UserProfile {
private String firstName;
private String lastName;
private Preferences preferences;// getters and setterspublic static class Preferences {private String theme;
private boolean notifications;
// getters and setters}
}
This POJO mirrors the JSON structure inside the profile field.
Mapping the Entity Using asentinel-orm
Now we create the main User class that maps to the users table:
public class User {
private Integer id;
private String username;
private UserProfile profile;// getters and setters}
Next, you’ll define a row mapper using the ORM:
RowMapper<User> userMapper = RowMapperBuilder
.of(User.class)
.field("id")
.field("username")
.jsonField("profile", UserProfile.class)
.build();
How jsonField Works
jsonField(columnName, targetClass) tells asentinel-orm to:
-
Read the column
profilefrom PostgreSQL. -
Deserialize the JSON/JSONB value into the Java
UserProfileclass using the configured JSON library. -
Populate the
Userobject automatically.
This eliminates manual parsing and keeps your domain model clean.
Inserting JSON Data Into PostgreSQL
To insert a new user with JSON data:
UserProfile profile = new UserProfile();
profile.setFirstName("Alice");
profile.setLastName("Rivers");UserProfile.Preferences prefs = new UserProfile.Preferences();prefs.setTheme(“dark”);
prefs.setNotifications(true);
profile.setPreferences(prefs);
User user = new User();user.setUsername(“alice123”);
user.setProfile(profile);
Then save using the ORM:
InsertOperation<User> insert = InsertOperationBuilder
.into("users")
.map(userMapper)
.build();insert.insert(user, connection);asentinel-orm will automatically serialize the Java object into JSON and send it as JSONB to PostgreSQL.
Updating JSON Fields
Updating works similarly:
UpdateOperation<User> update = UpdateOperationBuilder
.table("users")
.map(userMapper)
.where("id = :id")
.build();user.getProfile().getPreferences().setTheme(“light”);update.update(user, connection);
The ORM detects modified fields and serializes the updated JSON.
Reading JSON Data With Queries
To retrieve data:
QueryOperation<User> query = QueryOperationBuilder
.query("SELECT * FROM users WHERE id = :id")
.map(userMapper)
.build();User user = query.singleResult(Map.of(“id”, 1), connection);This gives you a fully populated User object with a deserialized UserProfile.
Using JSON Columns as Generic Maps
Sometimes, you may not want a strongly typed POJO. asentinel-orm also supports:
-
Map<String, Object> -
List<Object> -
JsonNode(if using Jackson)
Mapping JSON to a Map
RowMapper<User> mapper = RowMapperBuilder
.of(User.class)
.field("id")
.field("username")
.jsonField("profile", Map.class)
.build();
Now the profile field becomes a raw map.
You can also insert/update maps the same way:
Map<String, Object> profile = new HashMap<>();
profile.put("firstName", "Jordan");
profile.put("lastName", "Snow");user.setProfile(profile);Mapping JSON to Jackson’s JsonNode
If you prefer tree-based parsing:
RowMapper<User> mapper = RowMapperBuilder
.of(User.class)
.field("id")
.field("username")
.jsonField("profile", JsonNode.class)
.build();
JsonNode gives you full flexibility to inspect or transform the data dynamically.
Configuring Custom JSON Serialization
You can specify your own JSON mapper:
JsonMapper customMapper = new JsonMapper() {
private final ObjectMapper om = new ObjectMapper();
@Override
public <T> T fromJson(String json, Class<T> type) {
try {
return om.readValue(json, type);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String toJson(Object obj) {
try {
return om.writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
OrmConfig.setJsonMapper(customMapper);
This allows:
-
Custom date formats
-
Enum handling
-
Polymorphic types
-
Custom validators
Partial JSON Updates (Advanced Patterns)
PostgreSQL supports partial JSONB updates using operators. asentinel-orm allows you to run these operations via SQL while still mapping results.
Example:
QueryOperation<Integer> op = QueryOperationBuilder
.query("UPDATE users SET profile = jsonb_set(profile, '{preferences,theme}', '\"dark\"') WHERE id = :id")
.build();
This updates only a single nested value.
You could afterwards retrieve the updated object using the mapper.
Storing Arrays of JSON Objects
PostgreSQL also allows storing arrays of JSON/JSONB values. Suppose the schema is:
ALTER TABLE users ADD COLUMN roles JSONB[];
Then your Java side might look like:
private List<Role> roles;
Mapping:
jsonField("roles", new TypeReference<List<Role>>() {})
This requires passing a type reference rather than a class, a common approach when working with generics.
Transaction Support
When using JSON fields inside transactions, asentinel-orm behaves the same as with standard columns:
connection.setAutoCommit(false);
try {
insert.insert(user, connection);
update.update(user, connection);
connection.commit();
} catch (Exception e) {
connection.rollback();
}
JSON serialization does not impact ACID guarantees.
Common Pitfalls and Best Practices
Validate POJO Structure
Ensure your Java classes match the JSON structure expected by PostgreSQL.
Use JSONB Altogether
Avoid mixing JSON and JSONB in the same table unless absolutely necessary.
Avoid Over-nesting
Deeply nested JSON structures are harder to index and search.
Index JSONB Fields Carefully
Use:
CREATE INDEX ON users USING gin (profile jsonb_path_ops);
if planning to query by JSON fields.
Use Jackson Annotations for Stability
If your API changes, annotations can help maintain backward compatibility.
Conclusion
Mapping PostgreSQL JSON and JSONB data types in Java using asentinel-orm offers an elegant and efficient approach to handling semi-structured data while still benefiting from the robustness of a relational database ecosystem. The ORM’s lightweight design allows developers to bypass the complexity of heavier frameworks while still enjoying powerful features such as automatic JSON serialization, type-safe deserialization, and configurable JSON mappers.
By combining PostgreSQL’s advanced JSON capabilities with Java POJOs or flexible structures like Map and JsonNode, development teams can model dynamic or evolving data without sacrificing maintainability or performance. Whether you are storing user profiles, configuration settings, logs, or deep object graphs, asentinel-orm provides a smooth bridge between relational storage and Java’s strong typing.
The examples provided—ranging from simple POJO mapping to advanced type references, partial updates, and custom JSON serializers—illustrate how adaptable the ORM is to various needs. With minimal configuration and a clean programming model, you gain the ability to insert, update, query, and manipulate JSONB fields effortlessly.
In a world where data structures continue to evolve and hybrid relational-document storage becomes increasingly common, mastering JSON mapping in Java is essential. asentinel-orm delivers a practical, streamlined solution that integrates seamlessly with PostgreSQL’s powerful JSON capabilities, making it an excellent choice for modern Java application development.
If you build Java applications that rely on flexible data storage, embracing this approach will enable you to write cleaner code, reduce boilerplate, and maintain high performance—while keeping your data model expressive, modern, and easy to manage.