Feign ErrorDecoder : retrieve the original message
It is suggested to use input stream instead of reader and map it to your object.
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;
InputStream responseBodyIs = null;
try {
responseBodyIs = response.body().asInputStream();
ObjectMapper mapper = new ObjectMapper();
ExceptionMessage exceptionMessage = mapper.readValue(responseBodyIs, ExceptionMessage.class);
message = exceptionMessage.message;
} catch (IOException e) {
e.printStackTrace();
// you could also return an exception
return new errorMessageFormatException(e.getMessage());
}finally {
//It is the responsibility of the caller to close the stream.
try {
if (responseBodyIs != null)
responseBodyIs.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;
}
}
If you're like me and really just want the content out of a failed Feign call without all these custom decoders and boilerplate, there is a hacky way do this.
If we look at FeignException when it is being created and a response body exists, it assembles the exception message like so:
if (response.body() != null) {
String body = Util.toString(response.body().asReader());
message += "; content:\n" + body;
}
Therefore if you're after the response body, you can just pull it out by parsing the Exception message since it is delimited by a newline.
String[] feignExceptionMessageParts = e.getMessage().split("\n");
String responseContent = feignExceptionMessageParts[1];
And if you want the object, you can use something like Jackson:
MyResponseBodyPojo errorBody = objectMapper.readValue(responseContent, MyResponseBodyPojo.class);
I do not claim this is a smart approach or a best practice.
If you want to get the response payload body, with the Feign exception, just use this method:
feignException.contentUTF8();
Example:
try {
itemResponse = call(); //method with the feign call
} catch (FeignException e) {
logger.error("ResponseBody: " + e.contentUTF8());
}
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;
}
}