The error org.postgresql.util.PSQLException: ERROR: operator does not exist: my_enum_type = character varying can be fixed by using implicit conversions in PostgreSQL or using explicit conversions by passing the java.sql.Types.OTHER to Spring JDBC.

The exception you will receive

When using Spring JDBC or Spring Data JDBC and custom Java enum types, you might run into the following problem:

org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT * FROM your_table where enum_column = :enum_value;] nested exception is org.postgresql.util.PSQLException: ERROR: operator does not exist: my_enum_type = character varying

Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.

The exception can occur if you are having enums with either

  1. or you are trying to persist an Spring Data JDBC entity with a repository save method
  2. executing a query on your own with one of Spring JDBC’s jdbcTemplate methods like query

Making Spring Data JDBC’s save method work with enumerations

With Spring Data JDBC you have probably an SQL definition like

CREATE TYPE my_enum_type AS ENUM('VALUE_1','VALUE_2')

CREATE TABLE my_table (enum_column my_enum_type);

The belonging Java source code will look like

public enum MyEnumType {
	VALUE_1, VALUE_2
}

@Table("my_table")
public class MyEntity {
	private MyEnumType type;
	// getter & setter ...
}

@Repository
public class MyTableRepository extends CrudRepository</* ... */> {
}

@Controller
public class MyTableController {
	@Autowired
	MyTableRepository
    
	@GetMapping("/")
	public void save() {
		MyEntity entity = new MyEntity();
		entity.setType(MyEnumType.VALUE_1);
		repository.save(entity);
	}
}

As soon as you try to call repository.save you will receive an exception. The problem is, that Spring Data JDBC does not support enumerations at the moment. See Jens Schauder’s answer at stackoverflow.

Jens does also link to another SO answer which describes how to solve the problem. To make the code sample above working, we can use PostgreSQL’s implicit conversion feature as described in the linked answer. The following SQL definition would reside somewhere in your Liquibase or Flyway migration definition:

<!-- Liquibase migration definition -->
<!-- as described above -->
<sql>CREATE TYPE my_enum_type AS ENUM('VALUE_1','VALUE_2')</sql>

<!-- add an additional type -->
<sql>CREATE CAST (varchar AS my_enum_type) WITH INOUT AS IMPLICIT</sql>

With help of the described CREATE CAST PostgreSQL will automatically try convert each String/varchar into the specified enum. You can now do something like

SELECT * FROM my_table WHERE enum_column = 'VALUE_1';
-- OR
INSERT INTO my_table(enum_column) VALUES('VALUE_1')

After that, repository.save() will work.

Using JdbcTemplate and enumerations

You might think that the specified CREATE CAST definition would also work for something like that:

public class MyRepository {
	@Autowired
    NamedParameterJdbcTemplate jdbcTemplate;

	public List<String> findAll() {
		MapSqlParameterSource parameters = new MapSqlParameterSource("type", MyEnumType.VALUE_1);
		
		return jdbcTemplate
			.queryForList("SELECT enum_column FROM my_table WHERE enum_column = :type", parameters);
	}
}

But this will drive you right into the exception you find at the beginning:

org.postgresql.util.PSQLException: ERROR: operator does not exist: my_enum_type = character varying

The reason for this behavior is how PostgreSQL handles the type casts. This Stackoverflow answer describes the reasons in detail. Spring JDBC does automatically convert the enum value into a Java String type and assigns it to the prepared statement. The underlying PgJDBC drivers then assigns the java.sql.Type.VARCHAR as type for this prepared statement parameter. Due to the assignment of the java.sql.Type, PostgreSQL will no longer try to apply our CREATE CAST conversion.

Solving it by configuration

You can configure your JDBC URL to use the parameter stringtype with value undefined. Each String parameter previously set with setString() will then not have the type definition java.sql.Type.VARCHAR. PostgreSQL applies will then apply our CREATE CAST definition.

Solving it programatically

If you don’t globally want to set stringtype to undefined, you have to use the java.sql.Types.OTHER when adding a value to the MapSqlParameterSource:

// does NOT WORK: 
// .addValue("type", MyEnumType.VALUE_1))
// one of the following does work:
.addValue("type", "VALUE_1", java.sql.Types.OTHER)
// or
.addValue("type", MyEnumType.VALUE_1.getName(), java.sql.Types.OTHER)
// or
.addValue("type", MyEnumType.VALUE_1, java.sql.Types.OTHER)

To make it more convenient, you can extend from MapSqlParameterSource to get something like this:

public static class CustomMapSqlParameterSource extends MapSqlParameterSource {
	public CustomMapSqlParameterSource addEnum(String paramName, Object value) {
		if (!value.getClass().isEnum()) {
			throw new IllegalArgumentException("Given parameter is not of Java type enum");
		}

		addValue(paramName, value, java.sql.Types.OTHER);

		return this;
	}
}

Wrapping it up

This blog post showed you, how you can use native PostgreSQL enumerations with native Java and make them both work with Spring Data JDBC and Spring JDBC.

I am asking you for a donation...

You liked the content or this article has helped and reduced the amount of time you have struggled with this issue? Please donate a few bucks so I can keep going with my troubleshooting adventure :-)