I'm having an issue returning data from one of my Quarkus REST endpoints. I've reviewed several similar questions here on SO (Jackson: No serializer found for class ~~~~~ and no properties discovered to create BeanSerializer, Quarkus reactive endpoint "no serializer found", Jackson serialize complicated class and occur the exception No serializer found for class). All of these describe my issue perfectly, but so far none of the solutions presented have worked.
This REST service has several endpoints. The intention of this project is to be a central data library for several micro-frontends we are working on. Therefore, I have an almost duplicate endpoint which works as expected.
I explain what I mean by "non-working" below
From my resource class, here are the two endpoint methods: Working
@GET
@Path("/all")
@Produces(MediaType.APPLICATION_JSON)
public Stream<Event> getAllEvents(){
return service.getAllEvents();
}
Non-Working
@Path("/display")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Stream<EmployeeOption> getOptionList(){
try {
return service.getOptionList();
} catch (SQLException e) {
throw new EventException("Unable to retrieve associate list",e);
}
}
Code from my repository classes Working
public Stream<ActiveEvent> getActiveEvents() throws EventException{
List<ActiveEvent> retVal = new ArrayList<>();
try(Connection cn = eventsDs.getConnection();
PreparedStatement ps = cn.prepareStatement(All_EVENTS)){
ResultSet rs = ps.executeQuery();
while(rs.next()){
ActiveEvent item = new ActiveEvent();
item.setEventId(rs.getString("eid"));
item.setEventDescription(rs.getString("edescr"));
retVal.add(item);
}
}catch (SQLException e){
throw new EventException("Opps! Some unexplained, missed exception occurred", e);
}
return retVal.stream().sorted(Comparator.naturalOrder());
}
Non-working
public Stream<EmployeeOption> getEmployeeList() throws SQLException {
List<EmployeeOption> retVal = new ArrayList<>();
try (Connection cn = empDs.getConnection();
PreparedStatement ps = cn.prepareStatement(DROPDOWN_SQL)){
ResultSet rs = ps.executeQuery();
while (rs.next()) {
EmployeeOption e = new EmployeeOption();
e.setUserId(rs.getString("user"));
e.setDisplay(rs.getString("display"));
retVal.add(e);
}
}
return retVal.stream().sorted(EmployeeOption::compareTo);
}
Model Classes Working
package com.goodyear.events.models;
import java.util.Objects;
public class ActiveEvent implements Comparable<ActiveEvent> {
private String eventId;
private String eventDescription;
public String getEventId() {
return eventId;
}
public void setEventId(String eventId) {
this.eventId = eventId;
}
public String getEventDescription() {
return eventDescription;
}
public void setEventDescription(String eventDescription) {
this.eventDescription = eventDescription;
}
public int getActiveEventId(){
return Integer.parseInt(eventId);
}
@Override
public int compareTo(ActiveEvent o) {
return Integer.parseInt(this.eventId) - Integer.parseInt(o.eventId);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ActiveEvent)) {
return false;
}
ActiveEvent ae = (ActiveEvent) o;
return getActiveEventId() == ae.getActiveEventId();
}
@Override
public int hashCode() {
return Objects.hash(getActiveEventId());
}
}
Non-working
package com.goodyear.events.models;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class EmployeeOption implements Comparable<EmployeeOption> {
private String userId;
private String display;
public String userId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String display() {
return display;
}
public void setDisplay(String display) {
this.display = display;
}
@Override
public int compareTo(EmployeeOption o) {
return userId.compareTo(o.userId);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof EmployeeOption)) {
return false;
}
EmployeeOption that = (EmployeeOption) o;
return userId.equals(that.userId);
}
@Override
public int hashCode() {
return 0;
}
}
The service class simply passes the request on to the respective repository which will provide the data.
By non working I mean the endpoint method will either throw
2022-09-15 07:33:06,389 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-0) HTTP Request to /data/display failed, error id: eb3d8291-c38b-4951-88d3-12c697b5da3b-2: org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.goodyear.events.models.EmployeeOption and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:368)
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:218)
at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:614)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:524)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:91)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:557)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.goodyear.events.models.EmployeeOption and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:46)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:29)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:1142)
at com.fasterxml.jackson.datatype.jdk8.StreamSerializer.lambda$serialize$0(StreamSerializer.java:74)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEachOrdered(ReferencePipeline.java:601)
at com.fasterxml.jackson.datatype.jdk8.StreamSerializer.serialize(StreamSerializer.java:71)
at com.fasterxml.jackson.datatype.jdk8.StreamSerializer.serialize(StreamSerializer.java:15)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:400)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1514)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1007)
at org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider.writeTo(ResteasyJackson2Provider.java:345)
at org.jboss.resteasy.core.messagebody.AsyncBufferedMessageBodyWriter.asyncWriteTo(AsyncBufferedMessageBodyWriter.java:24)
at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.writeTo(ServerWriterInterceptorContext.java:87)
at org.jboss.resteasy.core.interception.jaxrs.AbstractWriterInterceptorContext.asyncProceed(AbstractWriterInterceptorContext.java:203)
at org.jboss.resteasy.core.interception.jaxrs.AbstractWriterInterceptorContext.getStarted(AbstractWriterInterceptorContext.java:166)
at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.lambda$getStarted$0(ServerWriterInterceptorContext.java:73)
at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.aroundWriteTo(ServerWriterInterceptorContext.java:93)
at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.getStarted(ServerWriterInterceptorContext.java:73)
at org.jboss.resteasy.core.ServerResponseWriter.lambda$writeNomapResponse$3(ServerResponseWriter.java:163)
at org.jboss.resteasy.core.interception.jaxrs.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:410)
at org.jboss.resteasy.core.ServerResponseWriter.executeFilters(ServerResponseWriter.java:252)
at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:101)
at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:74)
at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:594)
... 16 more
Or, based on the recommended solution, will return an empty array. The service returns data to the endpoint method but then it is somehow erased when streamed back to the caller.
I simply do not see what the difference is that would cause this behavior.