spring-cloud-feign

Feign ErrorDecoder : retrieve the original message


I use a ErrorDecoder to return the right exception rather than a 500 status code.

Is there a way to retrieve the original message inside the decoder. I can see that it is inside the FeignException, but not in the decode method. All I have is the 'status code' and a empty 'reason'.

public class CustomErrorDecoder implements ErrorDecoder {

    private final ErrorDecoder errorDecoder = new Default();

    @Override
    public Exception decode(String s, Response response) {

        switch (response.status()) {

            case 404:
                return new FileNotFoundException("File no found");
            case 403:
                return new ForbiddenAccessException("Forbidden access");
        }

        return errorDecoder.decode(s, response);
    }
}

Here the original message : "message":"Access to the file forbidden"

feign.FeignException: status 403 reading ProxyMicroserviceFiles#getUserRoot(); content:
{"timestamp":"2018-11-28T17:34:05.235+0000","status":403,"error":"Forbidden","message":"Access to the file forbidden","path":"/root"}

Also I use my FeignClient interface like a RestController so I don't use any other Controler populated with the proxy that could encapsulate the methods calls.

   @RestController
   @FeignClient(name = "zuul-server")
   @RibbonClient(name = "microservice-files")

   public interface ProxyMicroserviceFiles {

                @GetMapping(value = "microservice-files/root")
                Object getUserRoot();

                @GetMapping(value = "microservice-files/file/{id}")
                Object getFileById(@PathVariable("id") int id);

    }

Solution

  • Here is a solution, the message is actually in the response body as a stream.

    package com.clientui.exceptions;
    
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.io.CharStreams;
    import feign.Response;
    import feign.codec.ErrorDecoder;
    import lombok.*;
    
    import java.io.*;
    
    public class CustomErrorDecoder implements ErrorDecoder {
    
        private final ErrorDecoder errorDecoder = new Default();
    
        @Override
        public Exception decode(String s, Response response) {
    
            String message = null;
            Reader reader = null;
    
            try {
                reader = response.body().asReader();
                //Easy way to read the stream and get a String object
                String result = CharStreams.toString(reader);
                //use a Jackson ObjectMapper to convert the Json String into a 
                //Pojo
                ObjectMapper mapper = new ObjectMapper();
                //just in case you missed an attribute in the Pojo     
              mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
                //init the Pojo
                ExceptionMessage exceptionMessage = mapper.readValue(result, 
                                                    ExceptionMessage.class);
    
                message = exceptionMessage.message;
    
            } catch (IOException e) {
    
                e.printStackTrace();
            }finally {
    
                //It is the responsibility of the caller to close the stream.
                try {
    
                    if (reader != null)
                        reader.close();
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            switch (response.status()) {
    
                case 404:
                    return new FileNotFoundException(message == null ? "File no found" : 
                                                                         message);
                case 403:
                    return new ForbiddenAccessException(message == null ? "Forbidden 
                                                                  access" : message);
    
            }
    
            return errorDecoder.decode(s, response);
        }
    
        @Getter
        @Setter
        @NoArgsConstructor
        @AllArgsConstructor
        @ToString
        public static class ExceptionMessage{
    
            private String timestamp;
            private int status;
            private String error;
            private String message;
            private String path;
    
        }
    }