Skip to content

How to Use JSON Column Types with Spring Data JPA

Purpose

This post demonstrates how to use JSON column types with Spring Data JPA. JSON columns let you store complex data structures directly in database columns.

Environment

  • Spring Boot 3.2.x
  • Java 21
  • PostgreSQL 15
  • Spring Data JPA 3.2.x

The Problem

When building applications, I need flexible data structures. Sometimes I don’t want to create separate database tables for related data. JSON columns solve this problem.

JSON<List<String>> lets me store collections as JSON arrays. No need for join tables or complex relationships.

Database Setup

First, my database must support JSON columns. PostgreSQL works well for this.

-- Create table with JSON columns
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
tags JSON, -- JSON array of strings
attributes JSONB -- JSON object
);
-- Index for JSON queries
CREATE INDEX idx_product_tags ON products USING GIN(tags);
CREATE INDEX idx_product_attributes ON products USING GIN(attributes);

Entity Mapping

Here’s my entity with JSON fields:

Product.java
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(columnDefinition = "JSON")
private List<String> tags;
@Column(columnDefinition = "JSON")
private Map<String, Object> attributes;
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public Map<String, Object> getAttributes() { return attributes; }
public void setAttributes(Map<String, Object> attributes) { this.attributes = attributes; }
}

JSON Collections Support

Spring CRUD Generator v1.2.0 adds support for JSON collections. This means I can store lists and sets directly as JSON.

ProductRepository.java
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
// Find products by tag
@Query("SELECT p FROM Product p WHERE JSON_CONTAINS(p.tags, :tag)")
List<Product> findByTag(@Param("tag") String tag);
// Find products by JSON attribute
@Query("SELECT p FROM Product p WHERE JSON_EXTRACT(p.attributes, '$.color') = :color")
List<Product> findByColor(@Param("color") String color);
// Find products with specific variant
@Query("SELECT p FROM Product p WHERE JSON_CONTAINS(p.variants, :variantId, '$.id')")
List<Product> findByVariantId(@Param("variantId") Long variantId);
}

Service Layer

My service handles the business logic:

ProductService.java
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product createProduct(ProductDTO dto) {
Product product = new Product();
product.setName(dto.getName());
product.setTags(dto.getTags()); // `JSON<List<String>>`
product.setAttributes(dto.getAttributes()); // `JSON<Map<String, Object>>`
return productRepository.save(product);
}
public List<Product> searchByTag(String tag) {
return productRepository.findByTag(tag);
}
}

DTO Pattern

I use DTOs to control data exposure:

ProductDTO.java
public class ProductDTO {
private String name;
private List<String> tags;
private Map<String, Object> attributes;
// Nested `JSON<List<ProductVariant>>`
private List<ProductVariantDTO> variants;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public Map<String, Object> getAttributes() { return attributes; }
public void setAttributes(Map<String, Object> attributes) { this.attributes = attributes; }
public List<ProductVariantDTO> getVariants() { return variants; }
public void setVariants(List<ProductVariantDTO> variants) { this.variants = variants; }
}

Database Storage Format

When I save data, JSON columns store like this:

-- Tags become JSON array
["spring-boot", "jpa", "json", "database"]
-- Attributes become JSON object
{
"color": "blue",
"size": "large",
"weight": 1.5
}
-- Nested objects become JSON array of objects
[
{
"id": 1,
"name": "Variant 1",
"price": 19.99
},
{
"id": 2,
"name": "Variant 2",
"price": 24.99
}
]

JSON Conversion

Sometimes I need custom JSON conversion. Here’s how I handle it:

JsonConverter.java
@Converter
public class JsonConverter implements AttributeConverter<Object, String> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(Object attribute) {
try {
return objectMapper.writeValueAsString(attribute);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error converting to JSON", e);
}
}
@Override
public Object convertToEntityAttribute(String dbData) {
try {
return objectMapper.readValue(dbData, Object.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error parsing JSON", e);
}
}
}

When to Use JSON Columns

I use JSON columns for:

  • Product attributes that vary by product
  • User profiles with dynamic fields
  • Configuration settings
  • Tag systems
  • Feature flags

Performance Considerations

JSON queries can be slow. I index my JSON columns:

CREATE INDEX idx_product_tags ON products USING GIN(tags);

I also limit JSON size. Large JSON objects impact performance.

Security

JSON data can contain malicious content. I validate all JSON data before storing it.

// Validate JSON data
if (dto.getTags() != null) {
dto.getTags().forEach(tag -> {
if (tag == null || tag.trim().isEmpty()) {
throw new IllegalArgumentException("Invalid tag");
}
});
}

Summary

In this post, I showed how to use JSON column types with Spring Data JPA. The key point is JSON<List<Type>> stores collections directly in database columns without separate tables.

When implementing JSON columns:

  • Choose a database that supports JSON
  • Index JSON columns for performance
  • Validate JSON data for security
  • Use DTOs to control data exposure
  • Keep JSON objects reasonable in size

The Spring CRUD Generator v1.2.0 simplifies this with automatic JSON<List<Type>> support. Less boilerplate code, more flexible data structures.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments