'How to return custom response on javax.json.bind.JsonbException (Helidon project)
I'm trying to return a custom http response(400 bad request) instead of http 500 server error when json payload is not valid.
The problem is that when I use javax.json.bind.JsonbException
as exception type in ExceptionMapper<T>
it's not able to catch it, but I can catch the more generic javax.ws.rs.ProcessingException
.
It is obvious that the javax.ws.rs.ProcessingException
is caused by javax.json.bind.JsonbException
as stack trace is showing:
javax.ws.rs.ProcessingException: Error deserializing object from entity stream.
at org.glassfish.jersey.jsonb.internal.JsonBindingProvider.readFrom(JsonBindingProvider.java:86)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.invokeReadFrom(ReaderInterceptorExecutor.java:233)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:212)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundReadFrom(MappableExceptionWrapperInterceptor.java:49)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1072)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:885)
at org.glassfish.jersey.server.ContainerRequest.readEntity(ContainerRequest.java:290)
at org.glassfish.jersey.server.internal.inject.EntityParamValueParamProvider$EntityValueSupplier.apply(EntityParamValueParamProvider.java:73)
at org.glassfish.jersey.server.internal.inject.EntityParamValueParamProvider$EntityValueSupplier.apply(EntityParamValueParamProvider.java:56)
at org.glassfish.jersey.server.spi.internal.ParamValueFactoryWithSource.apply(ParamValueFactoryWithSource.java:50)
at org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:68)
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$AbstractMethodParamInvoker.getParamValues(JavaResourceMethodDispatcherProvider.java:109)
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$VoidOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:159)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:475)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:397)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)
at io.helidon.webserver.jersey.JerseySupport$JerseyHandler.lambda$doAccept$4(JerseySupport.java:326)
at io.helidon.common.context.Contexts.runInContext(Contexts.java:117)
at io.helidon.common.context.ContextAwareExecutorImpl.lambda$wrap$7(ContextAwareExecutorImpl.java:154)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: javax.json.bind.JsonbException: Unable to deserialize property 'date' because of: Error parsing class java.util.Date from value: 2022-03-31T10:37:23.005Z[UTC]x. Check your @JsonbDateFormat has all time units for class java.util.Date type, or consider using org.eclipse.yasson.YassonConfig#ZERO_TIME_PARSE_DEFAULTING.
at org.eclipse.yasson.internal.serializer.AbstractContainerDeserializer.deserializeInternal(AbstractContainerDeserializer.java:100)
at org.eclipse.yasson.internal.serializer.AbstractContainerDeserializer.deserialize(AbstractContainerDeserializer.java:64)
at org.eclipse.yasson.internal.Unmarshaller.deserializeItem(Unmarshaller.java:62)
at org.eclipse.yasson.internal.Unmarshaller.deserialize(Unmarshaller.java:51)
at org.eclipse.yasson.internal.JsonBinding.deserialize(JsonBinding.java:59)
at org.eclipse.yasson.internal.JsonBinding.fromJson(JsonBinding.java:99)
at org.glassfish.jersey.jsonb.internal.JsonBindingProvider.readFrom(JsonBindingProvider.java:84)
... 33 more
Caused by: javax.json.bind.JsonbException: Error parsing class java.util.Date from value: 2022-03-31T10:37:23.005Z[UTC]x. Check your @JsonbDateFormat has all time units for class java.util.Date type, or consider using org.eclipse.yasson.YassonConfig#ZERO_TIME_PARSE_DEFAULTING.
at org.eclipse.yasson.internal.serializer.AbstractDateTimeDeserializer.deserialize(AbstractDateTimeDeserializer.java:74)
at org.eclipse.yasson.internal.serializer.AbstractValueTypeDeserializer.deserialize(AbstractValueTypeDeserializer.java:64)
at org.eclipse.yasson.internal.serializer.ObjectDeserializer.deserializeNext(ObjectDeserializer.java:180)
at org.eclipse.yasson.internal.serializer.AbstractContainerDeserializer.deserializeInternal(AbstractContainerDeserializer.java:94)
... 39 more
Caused by: java.time.format.DateTimeParseException: Text '2022-03-31T10:37:23.005Z[UTC]x' could not be parsed, unparsed text found at index 29
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2055)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1954)
at java.base/java.time.ZonedDateTime.parse(ZonedDateTime.java:600)
at org.eclipse.yasson.internal.serializer.DateTypeDeserializer.parseWithOrWithoutZone(DateTypeDeserializer.java:79)
at org.eclipse.yasson.internal.serializer.DateTypeDeserializer.parseDefault(DateTypeDeserializer.java:49)
at org.eclipse.yasson.internal.serializer.DateTypeDeserializer.parseDefault(DateTypeDeserializer.java:29)
at org.eclipse.yasson.internal.serializer.AbstractDateTimeDeserializer.deserialize(AbstractDateTimeDeserializer.java:72)
... 42 more
But I was wondering if there is a way to catch the javax.json.bind.JsonbException
?
I am using Helidon-MP 2.4.2.
Reproduce:
- create a quick start of Helidon-MP Link
- add a post method on greeting resource
@POST
@Consumes(MediaType.APPLICATION_JSON)
public void add(MyClass cal){
System.out.println(cal.getDate());
}
public class MyClass {
private String name;
private Date date;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public MyClass(String name, Date date) {
this.name = name;
this.date = date;
}
public MyClass() {
}
}
3.send a post request with invalid json date. valid payoad:
{
"name": "testg",
"date": "2022-03-31T10:37:23.005Z[UTC]"
}
invalid (date) payload:
{
"name": "testg",
"date": "2022-03-31T10:37:23.005Z[UTC]XXXXXXXX"
}
Solution 1:[1]
I've been working in similar trouble, and i got this solution following this one previous solution (Jersey: Returning 400 error instead of 500 when given invalid request body).
import java.time.format.DateTimeParseException;
import javax.json.bind.JsonbException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.apache.johnzon.mapper.MapperException;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
@Provider
public class InvalidDataMapper implements ExceptionMapper<JsonbException> {
@Override
public Response toResponse(JsonbException arg0) {
if (arg0.getCause() instanceof MapperException) {
MapperException exception = (MapperException) arg0.getCause();
ErrorMessage errorMessage = null;
if (exception.getCause() instanceof DateTimeParseException) {
errorMessage = new ErrorMessage(CustomCodesAndMessages.CODE_ERROR_FORMAT_DATE, exception.getMessage());
}
if (exception.getCause() instanceof NumberFormatException) {
errorMessage = new ErrorMessage(CustomCodesAndMessages.CODE_ERROR_FORMATO_NUMERIC, exception.getMessage());
}
return Response.status(Response.Status.BAD_REQUEST).type(APPLICATION_JSON_TYPE).entity(errorMessage).build();
}
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
And ErrorMessage is
package fif.tech.utiles;
import java.util.Objects;
import org.apache.cxf.jaxrs.ext.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorMessage {
private final int code;
private final String message;
private final String details;
public ErrorMessage(String message) {
this(500, message);
}
public ErrorMessage(int code, String message) {
this(code, message, null);
}
@JsonCreator
public ErrorMessage(@JsonProperty("code") int code, @JsonProperty("message") String message,
@Nullable @JsonProperty("details") String details) {
this.code = code;
this.message = message;
this.details = details;
}
@JsonProperty("code")
public Integer getCode() {
return code;
}
@JsonProperty("message")
public String getMessage() {
return message;
}
@JsonProperty("details")
@Nullable
public String getDetails() {
return details;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
final ErrorMessage other = (ErrorMessage) obj;
return Objects.equals(code, other.code)
&& Objects.equals(message, other.message)
&& Objects.equals(details, other.details);
}
@Override
public int hashCode() {
return Objects.hash(code, message, details);
}
@Override
public String toString() {
return "ErrorMessage{code=" + code + ", message='" + message + "', details='" + details + "'}";
}
}
Now i'm trying to catch the detailed field that produce the exception, but i think that is a enough solution.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | rfrp |