Exception Serializer in Lagom

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 deserialization exception since the response with 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 which 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 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. 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.

public class Fault {
    @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 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.

References :

https://www.lagomframework.com/documentation/1.3.x/java/ServiceErrorHandling.html

knoldus-advt-sticker1

 

Written by 

Sajit is a Software Consultant having an experience of more than a year. He is a Java and Android enthusiast and has the knowledge of various programming languages. He is familiar with Object Oriented Programming Paradigms. His hobbies include gaming (FPS and multi-player), watching Hollywood movies and series, watching cricket, reading articles on Quora and listening to pop music - instrumental covers or theme songs.

Leave a Reply

%d bloggers like this: