I’m facing a strange error in a REST server application when trying to store an entity instance in a database:
The entity contains a map with enums as keys and strings as values. Both the entity and the enum class come from generated code via an OpenAPI YAML file; I only added a few annotations such as @Entity, @Id, @ElementCollection etc. so that I’m able to store and retrieve data via Hibernate. So far, so good, calling my REST server code to store and retrieve instances via the REST interface works.
When I add the Jackson databind annotations @JsonSerialize to the entity map’s getter method and @JsonDeserialize to the corresponding setter method to use a custom (de-)serializer for the map values, storing an entity instance in my server code now fails in its database layer:
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Enum (java.lang.Integer and java.lang.Enum are in module java.base of loader 'bootstrap')
at org.hibernate.type.descriptor.java.EnumJavaType.unwrap(EnumJavaType.java:36)
at org.hibernate.type.descriptor.jdbc.TinyIntJdbcType$1.doBind(TinyIntJdbcType.java:75)
at org.hibernate.type.descriptor.jdbc.BasicBinder.bind(BasicBinder.java:61)
at org.hibernate.engine.jdbc.mutation.internal.JdbcValueBindingsImpl.lambda$beforeStatement$0(JdbcValueBindingsImpl.java:87)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.hibernate.engine.jdbc.mutation.spi.BindingGroup.forEachBinding(BindingGroup.java:51)
at org.hibernate.engine.jdbc.mutation.internal.JdbcValueBindingsImpl.beforeStatement(JdbcValueBindingsImpl.java:85)
at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:130)
at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:55)
at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55)
at org.hibernate.persister.collection.mutation.InsertRowsCoordinatorStandard.insertRows(InsertRowsCoordinatorStandard.java:117)
at org.hibernate.persister.collection.BasicCollectionPersister.recreate(BasicCollectionPersister.java:119)
at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:47)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:632)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:499)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:371)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:41)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1425)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:487)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2324)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1981)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
at org.hibernate.internal.TransactionManagement.commit(TransactionManagement.java:65)
at org.hibernate.internal.TransactionManagement.manageTransaction(TransactionManagement.java:23)
at org.hibernate.SessionFactory.lambda$inTransaction$0(SessionFactory.java:237)
at org.hibernate.SessionFactory.inSession(SessionFactory.java:217)
at org.hibernate.SessionFactory.inTransaction(SessionFactory.java:237)
at com.example.DatabaseManager.insert(DatabaseManager.java:235)
(...)
Store method in my database layer:
public <T> void insert(T data) throws PersistenceException {
try {
this.sessionFactory.inTransaction(session -> session.persist(data));
} catch (Exception ex) {
throw new PersistenceException("Error saving object", ex);
}
}
Enum class:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public enum MyEnum {
ONE(1), TWO(2), THREE(3);
private Integer value;
MyEnum(Integer value) {
this.value = value;
}
public static MyEnum fromString(String s) {
for (MyEnum b : MyEnum.values()) {
if (java.util.Objects.toString(b.value).equals(s)) {
return b;
}
}
throw new IllegalArgumentException("Unexpected string value '" + s + "'");
}
@Override
@JsonValue
public String toString() {
return String.valueOf(value);
}
@JsonCreator
public static MyEnum fromValue(Integer value) {
for (MyEnum b : MyEnum.values()) {
if (b.value.equals(value)) {
return b;
}
}
throw new IllegalArgumentException("Unexpected value '" + value + "'");
}
}
Entity class:
@Entity
public class MyEntity {
private @Valid @Id @GeneratedValue Long id;
private @ElementCollection(fetch = FetchType.EAGER) Map<MyEnum, String> data;
public Long getId() {
return id;
}
public void setId(Long pId) {
this.id = pId;
}
@JsonProperty("data")
// @JsonSerialize(using = MapToArraySerializer.class)
public Map<MyEnum, String> getData() {
return this.data;
}
@JsonProperty("data")
// @JsonDeserialize(using = ArrayToMapDeserializer.class)
public void setData(Map<MyEnum, String> pData) {
this.data = pData;
}
public void addData(MyEnum key, String value) {
if (null == this.data) {
this.data = new HashMap<>();
}
this.data.put(key, value);
}
}
I’ve verified this via a JUnit test that starts up the REST server code before any tests are executed.
What puzzles me:
Annotations are uncommented:
- The unit test calls the insert method in the database layer directly => works
- The unit test triggers a REST API call so that the server code tries to insert the entity => exception
Annotations are commented out:
Inserting works in both cases.
Do you have any idea what I’m doing wrong and/or what is causing this behaviour?
Environment:
Hibernate ORM: 6.5.2.Final
RESTeasy 6.2.9.Final
Java 21
Database: PostgreSQL 16.2
Regards
Thorsten
PS: The above mentioned server application code is a simulator for a custom program we have to call via an OpenAPI REST interface. Although the API description says that the entity’s field is a map, the custom program implementation assumes it to be an array (!) with entries [ key_1, value_1, …, key_n, value_n ] with key_X/value_x being the data from map entry #X…