Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Lagom's Exception Serializer

DZone's Guide to

Lagom's Exception Serializer

See how you can use Lagom's ExceptionSerializer subclass to help map your error responses to POJOs will helpful messages.

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

In this blog, I will show you how to handle error responses from a Lagom service and convert them into well-defined exceptions using exception serializers.

Suppose we have an unmanaged service in Lagom. The unmanaged service hits a target service, and the success response is deserialized into a POJO corresponding to the service call. But in the case of an error response, we get a deserialization exception since the response with the details of the error cannot be mapped to that POJO.

Using Lagom’s ExceptionSerializer, we can map all the responses with status codes in the range of 400-500 to appropriate exceptions specific to the service with a well-defined message. It allows the client of the service to take a suitable action based on the exception generated.

Exception serializers convert exceptions to RawExceptionMessage. The raw exception message contains a status code, a message body, and a protocol descriptor that defines the content type of the message. It also allows an exception to be recreated from an error code and their serialized form.

This is how the exception serializer is defined:

public interface ExternalService extends Service {
    @Override
    default Descriptor descriptor() {
        return Service.named("external-service")
                .withCalls(
                        Service.restCall(Method.GET, "/user", this::getUser)
                )
                .withExceptionSerializer(ExternalServiceExceptionSerializer.INSTANCE)
                .withAutoAcl(true);
    }
    ServiceCall<NotUsed, User> getUser();
}


The ExceptionSerializer subclass needs to override the serialize and deserialize methods, which define how the exceptions are serialized to RawExceptionMessage and deserialized to Throwable. The following example shows how it can be done.

public class ExternalServiceExceptionSerializer implements ExceptionSerializer {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    public static final ExternalServiceExceptionSerializer INSTANCE = new ExternalServiceExceptionSerializer();

    @Override
    public RawExceptionMessage serialize(Throwable exception, Collection accept) {
        return null;
    }

    @Override
    public Throwable deserialize(RawExceptionMessage message) {
        return ExceptionFactory.getInstance(mapExceptionToFault(message));
    }

    Fault mapExceptionToFault(RawExceptionMessage message) {
        Fault fault;
        try {
            String errorJson = message.messageAsText();
            fault = OBJECT_MAPPER.readValue(errorJson, Fault.class);
        } catch (IOException ex) {

            fault = Fault.builder()
                    .errorMessage("No payload available")
                    .build();
        }
        return fault;
    }
}


The above class takes the serialized exception message (RawExceptionMessage) and maps it to a Fault POJO containing the error string.

publicclassFault {
    @JsonProperty("message")
    String errorMessage;
}


The Fault POJO is provided to an exception factory class, which determines the exception to throw based on the contents of the error message.

public class ExceptionFactory {

    public static Throwable getInstance(Fault fault) {
        if (fault == null)
            return null;
        return determineException(fault);
    }

    private static Throwable determineException(Fault fault) {
        String message = fault.getErrorMessage();

        if (StringUtils.isNotEmpty(message) && message.contains("Requires authentication")) {
            return new AuthenticationException(fault);
        }
        return new GenericException(fault);
    }

    public static class AuthenticationException extends RuntimeException {

        Fault fault;
        public AuthenticationException(Fault fault) {
            this.fault = fault;
        }
    }

    public static class GenericException extends RuntimeException {

        Fault fault;
        public GenericException(Fault fault) {
            this.fault = fault;
        }
    }
}


That is it. We have now successfully implemented an exception serializer in our service. Now the service will always throw the custom exceptions specified in the exception factory whenever the backend service gives the error response.

Hope you enjoyed reading. The complete code is available here.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
lagom ,java ,exception handling ,exception serializer ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}