diff --git a/pom.xml b/pom.xml
index eeaa0b9e93..8665dcb5a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,7 @@
8.0.23
42.2.19
19.6.0.0
+ 2.12.3
4.0.3
diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml
index af9ad0904e..5de0c066bc 100644
--- a/spring-data-jdbc/pom.xml
+++ b/spring-data-jdbc/pom.xml
@@ -144,6 +144,20 @@
true
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.databind.version}
+ true
+
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+ true
+
+
org.hsqldb
hsqldb
@@ -172,13 +186,6 @@
test
-
- org.postgresql
- postgresql
- ${postgresql.version}
- test
-
-
org.mariadb.jdbc
mariadb-java-client
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AbstractPostgresJsonReadingConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AbstractPostgresJsonReadingConverter.java
new file mode 100644
index 0000000000..b7fe632c9c
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AbstractPostgresJsonReadingConverter.java
@@ -0,0 +1,31 @@
+package org.springframework.data.jdbc.core.convert;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.postgresql.util.PGobject;
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * An abstract class for building your own converter for PostgerSQL's JSON[b].
+ *
+ * @author Nikita Konev
+ */
+public abstract class AbstractPostgresJsonReadingConverter implements Converter {
+ private final ObjectMapper objectMapper;
+ private final Class valueType;
+
+ public AbstractPostgresJsonReadingConverter(ObjectMapper objectMapper, Class valueType) {
+ this.objectMapper = objectMapper;
+ this.valueType = valueType;
+ }
+
+ @Override
+ public T convert(PGobject pgObject) {
+ try {
+ final String source = pgObject.getValue();
+ return objectMapper.readValue(source, valueType);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Unable to deserialize to json " + pgObject, e);
+ }
+ }
+}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AbstractPostgresJsonWritingConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AbstractPostgresJsonWritingConverter.java
new file mode 100644
index 0000000000..e380b790b1
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AbstractPostgresJsonWritingConverter.java
@@ -0,0 +1,34 @@
+package org.springframework.data.jdbc.core.convert;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.postgresql.util.PGobject;
+import org.springframework.core.convert.converter.Converter;
+import java.sql.SQLException;
+
+/**
+ * An abstract class for building your own converter for PostgerSQL's JSON[b].
+ *
+ * @author Nikita Konev
+ */
+public abstract class AbstractPostgresJsonWritingConverter implements Converter {
+ private final ObjectMapper objectMapper;
+ private final boolean jsonb;
+
+ public AbstractPostgresJsonWritingConverter(ObjectMapper objectMapper, boolean jsonb) {
+ this.objectMapper = objectMapper;
+ this.jsonb = jsonb;
+ }
+
+ @Override
+ public PGobject convert(T source) {
+ try {
+ final PGobject pGobject = new PGobject();
+ pGobject.setType(jsonb ? "jsonb" : "json");
+ pGobject.setValue(objectMapper.writeValueAsString(source));
+ return pGobject;
+ } catch (JsonProcessingException | SQLException e) {
+ throw new RuntimeException("Unable to serialize to json " + source, e);
+ }
+ }
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/PostgresJsonConvertersIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/PostgresJsonConvertersIntegrationTests.java
new file mode 100644
index 0000000000..eaee709ae8
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/PostgresJsonConvertersIntegrationTests.java
@@ -0,0 +1,184 @@
+package org.springframework.data.jdbc.core.convert;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.*;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.convert.CustomConversions;
+import org.springframework.data.convert.ReadingConverter;
+import org.springframework.data.convert.WritingConverter;
+import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
+import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
+import org.springframework.data.jdbc.testing.TestConfiguration;
+import org.springframework.data.mapping.model.SimpleTypeHolder;
+import org.springframework.data.relational.core.dialect.Dialect;
+import org.springframework.data.relational.core.mapping.Table;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test for PostgreSQL JSON[B] converters.
+ * Start this test with -Dspring.profiles.active=postgres
+ *
+ * @author Nikita Konev
+ */
+@EnabledIfSystemProperty(named = "spring.profiles.active", matches = "postgres")
+@ContextConfiguration
+@Transactional
+@ExtendWith(SpringExtension.class)
+public class PostgresJsonConvertersIntegrationTests {
+
+ @Profile("postgres")
+ @Configuration
+ @Import(TestConfiguration.class)
+ @EnableJdbcRepositories(considerNestedRepositories = true,
+ includeFilters = @ComponentScan.Filter(value = CustomerRepository.class, type = FilterType.ASSIGNABLE_TYPE))
+ static class Config {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Bean
+ Class> testClass() {
+ return PostgresJsonConvertersIntegrationTests.class;
+ }
+
+ @WritingConverter
+ static class PersonDataWritingConverter extends AbstractPostgresJsonWritingConverter {
+
+ public PersonDataWritingConverter(ObjectMapper objectMapper) {
+ super(objectMapper, true);
+ }
+ }
+
+ @ReadingConverter
+ static class PersonDataReadingConverter extends AbstractPostgresJsonReadingConverter {
+ public PersonDataReadingConverter(ObjectMapper objectMapper) {
+ super(objectMapper, PersonData.class);
+ }
+ }
+
+ @WritingConverter
+ static class SessionDataWritingConverter extends AbstractPostgresJsonWritingConverter {
+ public SessionDataWritingConverter(ObjectMapper objectMapper) {
+ super(objectMapper, true);
+ }
+ }
+
+ @ReadingConverter
+ static class SessionDataReadingConverter extends AbstractPostgresJsonReadingConverter {
+ public SessionDataReadingConverter(ObjectMapper objectMapper) {
+ super(objectMapper, SessionData.class);
+ }
+ }
+
+ private List