springspring-bootspring-dataspring-data-jdbc

Spring Data JDBC inserts varchar into Postgres jsonb column even with custom PGobject converter


I’m trying to persist a JSON object into a PostgreSQL jsonb column using Spring Data JDBC (not JPA). Even with a custom @WritingConverter that returns a PGobject of type jsonb, PostgreSQL errors out saying it’s receiving character varying.

Error

2025-10-07T08:42:33.310+05:30 ERROR ... DbActionExecutionException: Failed to execute InsertRoot{entity=Item(id=null, names={en=Book, fr=Livre, de=Buch}), idValueSource=GENERATED}
Caused by: org.postgresql.util.PSQLException: ERROR: column "names" is of type jsonb but expression is of type character varying
  Hint: You will need to rewrite or cast the expression.
  Position: 38
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2734) ~[postgresql-42.7.7.jar:42.7.7]

Environment

DDL

CREATE TABLE item (
    id BIGSERIAL PRIMARY KEY,
    names JSONB
);

Entity

@Table
@Data
public class Item {
    @Id
    private Long id;

    @Column("names")
    private Map<String, String> names;
}

Converters + Config (attempt 1)

@Configuration
public class JdbcConfig extends AbstractJdbcConfiguration {
    @Bean
    @Override
    public JdbcCustomConversions jdbcCustomConversions() {
        ObjectMapper objectMapper = new ObjectMapper();
        return new JdbcCustomConversions(Arrays.asList(
            new MapToJsonConverter(objectMapper),
            new JsonToMapConverter(objectMapper)
        ));
    }
}

@WritingConverter
public class MapToJsonConverter implements Converter<Map<String,String>, PGobject> {
    private final ObjectMapper objectMapper;
    public MapToJsonConverter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; }
    @Override
    public PGobject convert(Map<String,String> source) {
        try {
            PGobject pg = new PGobject();
            pg.setType("jsonb");
            pg.setValue(objectMapper.writeValueAsString(source));
            return pg;
        } catch (Exception e) { throw new RuntimeException(e); }
    }
}

@ReadingConverter
public class JsonToMapConverter implements Converter<PGobject, Map<String,String>> {
    private final ObjectMapper objectMapper;
    public JsonToMapConverter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; }
    @Override
    public Map<String,String> convert(PGobject source) {
        try {
            return source.getValue() == null ? null
                 : objectMapper.readValue(source.getValue(), new TypeReference<Map<String,String>>() {});
        } catch (JsonProcessingException e) { throw new RuntimeException(e); }
    }
}

Converters + Config (attempt 2)

@Configuration
public class JdbcConfig extends AbstractJdbcConfiguration {
    @Override
    protected List<?> userConverters() {
        ObjectMapper objectMapper = new ObjectMapper();
        return Arrays.asList(
            new MapToJsonConverter(objectMapper),
            new JsonToMapConverter(objectMapper)
        );
    }
}

Save call

Item item = new Item();
item.setNames(Map.of("en","Book","fr","Livre","de","Buch"));
itemRepository.save(item);

What I expect

Spring Data JDBC to apply MapToJsonConverter and bind a PGobject (Types.OTHER) so PostgreSQL accepts it as jsonb.

What actually happens

Postgres reports it receives character varying, which suggests my converter isn’t being invoked and the value is being sent as a plain String.

Any guidance or a minimal working example showing Spring Data JDBC + jsonb + custom converters would be super helpful. Thanks!

Any guidance or a minimal working example showing Spring Data JDBC + jsonb + custom converters would be super helpful. Thanks!


Solution

  • The issue is that Spring Data JDBC doesn't directly recognize PGobject as a valid converter return type. The framework converts your PGobject back to a String during parameter binding, which is why PostgreSQL sees character varying instead of jsonb.

    You need to wrap your PGobject in a JdbcValue to preserve the type information:

    @WritingConverter
    public class MapToJsonConverter implements Converter<Map<String, String>, JdbcValue> {
        private final ObjectMapper objectMapper;
    
        public MapToJsonConverter(ObjectMapper objectMapper) {
            this.objectMapper = objectMapper;
        }
    
        @Override
        public JdbcValue convert(Map<String, String> source) {
            try {
                PGobject pgObject = new PGobject();
                pgObject.setType("jsonb");
                pgObject.setValue(objectMapper.writeValueAsString(source));
                
                return JdbcValue.of(pgObject, JDBCType.OTHER);
            } catch (Exception e) {
                throw new RuntimeException("Failed to convert Map to JSONB", e);
            }
        }
    }