# Hibernate 6. Broken custom types

**URL:** <https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539>\
**Category:** Hibernate ORM\
**Created:** [April 24, 2023, 8:11am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539 "2023-04-24T08:11:08Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![bostandyksoft](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/bostandyksoft/32/1450_2.png) [@bostandyksoft](https://discourse.hibernate.org/u/bostandyksoft)\
**Post date:** [April 24, 2023, 8:11am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/1 "2023-04-24T08:11:09Z")

</div>

Hi.

I’ve updated to Hibernate 6.0.3.Final from 5.6.3.Final and got error when i tried to execute query with boolean field, that defined by Custom User Type. Hibernate query builder did not consider, that field described by Custom type and processed it just like Boolean

Example.  
I have entity with field, that processed different string values to “true” or “false”. In real life i’ve used Oracle and Y/N, but H2Database successfully processed this values, so i choosed ‘#’ and ‘&’ as symbols for “true” and “false”

```auto
@Entity
@Table(name = "CS_FOO")
public class Foo {

	private Long id;
	private boolean flag;

	@Id
	@SequenceGenerator(name = "CS_SEQ", sequenceName = "CS_SEQ")
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CS_SEQ")
	@Column(name = "ID")
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	@Column(name = "flag", updatable = false)
	@Type(
			value = StringBooleanType.class,
			parameters = {
					@org.hibernate.annotations.Parameter(name = "true", value = "#"),
					@org.hibernate.annotations.Parameter(name = "false", value = "&"),
			}
	)
	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

```

So there is implementation of StringBooleanType

```auto
public class StringBooleanType implements EnhancedUserType<Boolean>, ParameterizedType {

	private static final String T_VALUES = "true";
	private static final String F_VALUES = "false";
	private static final String NULLABLE = "nullable";
	private static final String CASE_SENSITIVE = "caseSensitive";

	private static final String[] DEFAULT_T_VALUES = {
			"TRUE",
			"T",
			"YES",
			"Y",
			"ON",
			"1"
	};

	private static final String[] DEFAULT_F_VALUES = {
			"FALSE",
			"F",
			"NO",
			"N",
			"OFF",
			"0"
	};
	public static final String NAME = "StringBoolean";

	private String[] tValues = DEFAULT_T_VALUES;
	private String[] fValues = DEFAULT_F_VALUES;
	private boolean caseSensitive;
	private boolean nullable;

	// ParameterizedType

	@Override
	public void setParameterValues(Properties parameters) {
		String tsProperty = parameters.getProperty(T_VALUES);
		if (tsProperty != null) {
			tValues = split(tsProperty);
			if (tValues.length == 0)
				tValues = DEFAULT_T_VALUES;
		}

		String fsProperty = parameters.getProperty(F_VALUES);
		if (fsProperty != null) {
			fValues = split(fsProperty);
		}

		caseSensitive = "true".equalsIgnoreCase(parameters.getProperty(CASE_SENSITIVE));
		nullable = "true".equalsIgnoreCase(parameters.getProperty(NULLABLE));
	}

	// UserType

	@Override
	public int getSqlType() {
		return Types.VARCHAR;
	}

	@Override
	public Class<Boolean> returnedClass() {
		return Boolean.class;
	}

	@Override
	public boolean equals(Boolean obj1, Boolean obj2) {
		if (obj1 == obj2)
			return true;
		if (nullable) {
			if (obj1 == null || obj2 == null)
				return false;
		} else {
			if (obj1 == null)
				return obj2.equals(Boolean.FALSE);
			if (obj2 == null)
				return obj1.equals(Boolean.FALSE);
		}
		return obj1.equals(obj2);
	}

	@Override
	public int hashCode(Boolean obj) throws HibernateException {
		return obj == null ? 0 : obj.hashCode();
	}

	@Override
	public Boolean nullSafeGet(ResultSet result, int pos, SharedSessionContractImplementor sessionImplementor, Object o) throws HibernateException, SQLException {
		String value = result.getString(pos);
		return valueOf(result.wasNull() ? null : value);
	}

	@Override
	public void nullSafeSet(PreparedStatement stmt, Boolean value, int index, SharedSessionContractImplementor sessionImplementor) throws HibernateException, SQLException {
		if (value == null) {
			stmt.setNull(index, Types.VARCHAR);
		} else {
			stmt.setString(index, toString(value));
		}
	}

	@Override
	public Boolean deepCopy(Boolean value) {
		return value;
	}

	@Override
	public boolean isMutable() {
		return false;
	}

	@Override
	public Serializable disassemble(Boolean value) {
		return value;
	}

	@Override
	public Boolean assemble(Serializable cached, Object owner) {
		return (Boolean) cached;
	}

	// EnhancedUserType
	@Override
	public String toSqlLiteral(Boolean value) {
		String text = toString(value);
		return text == null ? "NULL" : "'" + text + "'";
	}

	@Override
	public Boolean fromStringValue(CharSequence sequence) throws HibernateException {
		return valueOf(sequence);
	}

	// Privates

	private Boolean valueOf(CharSequence source) {
		if (source == null) {
			return nullable ? null : Boolean.FALSE;
		}
		String text = (String) source;

		if (caseSensitive) {
			for (String t : tValues) {
				if (t.equals(text)) {
					return Boolean.TRUE;
				}
			}

			if (nullable) {
				for (String f : fValues) {
					if (f.equals(text)) {
						return Boolean.FALSE;
					}
				}

				return null;
			}
		} else {
			for (String t : tValues) {
				if (t.equalsIgnoreCase(text)) {
					return Boolean.TRUE;
				}
			}

			if (nullable) {
				for (String f : fValues) {
					if (f.equalsIgnoreCase(text)) {
						return Boolean.FALSE;
					}
				}

				return null;
			}
		}

		return Boolean.FALSE;
	}

	@Override
	public String toString(Boolean value) {
		if (value == null) {
			return nullable ? null : fValues[0];
		}
		return value ? tValues[0] : fValues[0];
	}

	public static String[] split(String text) {
		StringTokenizer tokenizer = new StringTokenizer(text, " \t,;|");
		List<String> list = new ArrayList<>();
		while (tokenizer.hasMoreTokens()) {
			list.add(tokenizer.nextToken());
		}
		return list.toArray(new String[0]);
	}

}

```

Now, i want to select items, that have different values of field flag:

`query = s.createQuery("select f from Foo f where f.flag", Foo.class);`  
or  
`query = s.createQuery("select f from Foo f where not(f.flag)", Foo.class);`

I expect, that query analyzer will handle field’s type and tried to convert it by provided methods (for 5.6.3 worked version was f.flag=1 or f.flag=0)

But currently it raised exception “Wrong number” for my production Oracle and Data conversion error converting “CHARACTER VARYING to BOOLEAN” for H2

Best regards. Alexey

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [April 24, 2023, 8:47am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/2 "2023-04-24T08:47:54Z")

</div>

Update to 6.2.1.Final please and report again if you still have an issue.

---

<div class="post-metadata">

**Author:** ![bostandyksoft](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/bostandyksoft/32/1450_2.png) [@bostandyksoft](https://discourse.hibernate.org/u/bostandyksoft)\
**Post date:** [April 24, 2023, 9:27am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/3 "2023-04-24T09:27:30Z")

</div>

It reprodices for 6.2.1.Final

I’ve created issue in JIRA [[HHH-16504] - Hibernate JIRA](https://hibernate.atlassian.net/browse/HHH-16504)

---

<div class="post-metadata">

**Author:** ![gavenkoa](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/gavenkoa/32/537_2.png) [@gavenkoa](https://discourse.hibernate.org/u/gavenkoa)\
**Post date:** [April 30, 2023, 2:28pm UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/4 "2023-04-30T14:28:41Z")

</div>

@bostandyksoft Why don’t you utilize `org.hibernate.type.YesNoConverter`?

```
@Convert(converter = YesNoConverter.class)
private boolean active;

```

---

<div class="post-metadata">

**Author:** ![bostandyksoft](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/bostandyksoft/32/1450_2.png) [@bostandyksoft](https://discourse.hibernate.org/u/bostandyksoft)\
**Post date:** [May 1, 2023, 6:32am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/5 "2023-05-01T06:32:44Z")

</div>

Point is that we would have our custom type, that can use different values for Yes/No values.

---

<div class="post-metadata">

**Author:** ![gavenkoa](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/gavenkoa/32/537_2.png) [@gavenkoa](https://discourse.hibernate.org/u/gavenkoa)\
**Post date:** [May 1, 2023, 6:54am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/6 "2023-05-01T06:54:25Z")

</div>

Implementation of `YesNoConverter` is quite simple:

```auto
public class YesNoConverter implements 
    AttributeConverter<Boolean, Character>,
    BasicValueConverter<Boolean, Character> {

```

It could be used as an example, but I don’t understand why you went with `EnhancedUserType` instead…

---

<div class="post-metadata">

**Author:** ![bostandyksoft](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/bostandyksoft/32/1450_2.png) [@bostandyksoft](https://discourse.hibernate.org/u/bostandyksoft)\
**Post date:** [May 2, 2023, 1:50am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/7 "2023-05-02T01:50:06Z")

</div>

I.e. for Hibernate 6 should be used converters rather than custom types?

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [May 2, 2023, 7:53am UTC](https://discourse.hibernate.org/t/hibernate-6-broken-custom-types/7539/8 "2023-05-02T07:53:54Z")

</div>

`UserType` is considered legacy at this point. You should rather use `AttributeConverter` if possible. If you need to change how JDBC binding/extracting should work, consider using a custom `JdbcType`. If you need to change the mutability aspects of the Java type, us a custom `JavaType`. You can configure these types via `@JavaType(MyJavaType.class)` and `@JdbcType(MyJdbcType.class)`.
