1

I have a micro-services SpringBoot app in which i am having a hard time extracting the message of a FeignException from the feign client. I have tried the solution here, but when this line String result = CharStreams.toString(reader); is called, an IOException: stream is closed. is thrown. This is weird because i have used this exact code in a different project and no exception was thrown.

This is the custom exception from the called service:

public class CustomException extends RuntimeException {
    public CustomException(String s) {
        super(s);
    }
}

This is the custom exception response:

public class CustomExceptionResponse {
    private String message;
    private HttpStatus status;

    public CustomExceptionResponse(String message, HttpStatus status) {
        this.message = message;
        this.status = status;
    }

    //getters and setters
}

this is the custom exception handler in the called service:

@ControllerAdvice
@RestController
public class ExceptionsHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public final ResponseEntity<CustomExceptionResponse> handleCustomException(Exception ex, WebRequest request) {
        CustomExceptionResponse response = new CustomExceptionResponse(ex.getMessage(), HttpStatus.UNAUTHORIZED);
        return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED);
    }
}

and the exception is thrown here:

@Service
public class CustomServiceImpl implements CustomService {

    private CustomRepo repo;

    @Autowired
    public CustomServiceImpl(CustomRepo repo) {
        this.repo = repo;
    }

    @Override
    public int foo() {

        //some repo call
        if (condition)
            throw new CustomException("Custom Message");
        else
            //return some result
    }
}

if needed, i can add additional information

SpringBoot: 2.2.6 java: 1.8; springCloud: Hoxton.RELEASE NetflixEurekaClient: 2.2.2.RELEASE

EDIT This is the feign client class:

public interface CustomServiceClient {

    @GetMapping(value = "/custom/{arg}")
    ResponseEntity<CustomEntity> customCheck(@PathVariable("arg") Long arg);
}

and this is where it's called from:

public class CustomApiController implements CustomApi {

    private final SessionServiceImpl sessionService;
    private final CustomServiceClient customServiceClient;

    @Autowired
    public CustomApiController(CustomServiceClient customServiceClient,
                              SessionServiceImpl sessionService) {

        this.customServiceClient = customServiceClient;
        this.sessionService = sessionService;
    }

 @Override
    public ResponseEntity<CustomEntity> customCheck(@NotNull @Valid @PathVariable("arg") Long arg) {
        CallerCustomEntity entity;
        ResponseEntity<CustomEntity> customCheckResponse = customServiceClient
                .customCheck(arg);
        entity = Transformer.transformToModel(customCheckResponse.getBody());
        String sessionId = sessionService.createSession(entity);
        entity.setSessionId(sessionId);
        return ResponseEntity.ok(Transformer.transformToDto(entity));

    }


}

I also have this congfig class(in caller):

@Configuration
public class ClientConfig {
    @Autowired
    private Environment env;

    @PostConstruct
    private void configure() {
        // when the application runs in BCN a proxy is needed
        if (Boolean.valueOf(env.getProperty("client.proxy.enabled"))) {
            String proxyHost = env.getProperty("client.proxy.host");
            String proxyPort = env.getProperty("client.proxy.port");
            String proxyUser = env.getProperty("client.proxy.user");
            String proxyPassword = env.getProperty("client.proxy.password");
            String nonProxyHosts = env.getProperty("client.proxy.nonProxyHosts");

            Authenticator.setDefault(
                    new Authenticator() {
                        @Override
                        public PasswordAuthentication getPasswordAuthentication() {
                            return new PasswordAuthentication(
                                    proxyUser, proxyPassword.toCharArray());
                        }
                    }
            );

            System.setProperty("http.proxyHost", proxyHost);
            System.setProperty("http.proxyPort", proxyPort);
            System.setProperty("https.proxyHost", proxyHost);
            System.setProperty("https.proxyPort", proxyPort);
            System.setProperty("http.proxyUser", proxyUser);
            System.setProperty("http.proxyPassword", proxyPassword);
            System.setProperty("http.nonProxyHosts", nonProxyHosts); // for localhost and on-premise
        }
    }
}

and this config class(in caller):

@Configuration
public class FeignConfiguration {

    private static final int CONNECT_TIME_OUT_MILLIS = 150000;
    private static final int READ_TIME_OUT_MILLIS = 150000;

    @Bean
    public Request.Options options() {
        return new Request.Options(CONNECT_TIME_OUT_MILLIS, READ_TIME_OUT_MILLIS);
    }
}

this CORSFilter also(in caller):

@Component
public class SimpleCORSFilter implements Filter {

    private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class);

    public SimpleCORSFilter() {
        log.info("SimpleCORSFilter init");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Session-ID, Accept, X-Requested-With, remember-me, "
                + "Authentication");

        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

Stack trace:

java.io.IOException: stream is closed
    at java.base/sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.ensureOpen(HttpURLConnection.java:3478)
    at java.base/sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3503)
    at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:297)
    at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)
    at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188)
    at java.base/java.io.InputStreamReader.read(InputStreamReader.java:185)
    at java.base/java.io.Reader.read(Reader.java:229)
    at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1680)
    at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1659)
    at org.apache.commons.io.IOUtils.copy(IOUtils.java:1636)
    at org.apache.commons.io.IOUtils.toString(IOUtils.java:685)
    at com.example.webAppService.exception.decoder.FeignErrorDecoder.decode(FeignErrorDecoder.java:25)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:151)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
    at com.sun.proxy.$Proxy138.login(Unknown Source)
    at com.example.webAppService.controller.rest.CustomApiController.customCheck(CustomApiController.java:42)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.example.webAppService.client.rest.config.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:35)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:109)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:830)


EDIT 2:

this is the FeignErrorDecoder in the caller as seen here:

@Component
public class FeignErrorDecoder implements ErrorDecoder {


    @Override
    public Exception decode(String methodKey, Response response) {
        Reader reader = null;

        try {
            reader = response.body().asReader();
            String result = CharStreams.toString(reader);//this line throws the exception because the InputStream from reader has closed=true:
            JSONObject jsonObject = new JSONObject(result);
            if(jsonObject.has("httpStatus") && jsonObject.has("message")){
                final HttpStatus status = HttpStatus.valueOf(jsonObject.get("httpStatus").toString());
                final String message = jsonObject.get("message").toString();
                return new ResponseStatusException(status,message);
            }
            else{
                return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Communication Error");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return new Exception("Internal server Error");
    }
}
Sebilix
  • 11
  • 3
  • Where is `IOException: stream is closed` exception appearing? In the callee or the caller? – Michael Kreutz May 01 '20 at 08:36
  • Why do you anootate class `ExceptionsHandler` with `@RestController`? – Michael Kreutz May 01 '20 at 08:38
  • @MichaelKreutz in the caller (the feignClient); about the `ExceptionsHandler`, i found some example and there it was adnotated like this – Sebilix May 01 '20 at 09:26
  • Can you share the code where you use the feign client? Can you also share the API endpoint that you call with feign client? – Michael Kreutz May 01 '20 at 09:34
  • Concerning the class `ExceptionHandler`, if there are no REST-endpoints in this class then remove `@RestController`. However you could consider using `@RestControllerAdvice` instead of `@ControllerAdvice`. – Michael Kreutz May 01 '20 at 10:47
  • Can you share the stack trace? – Michael Kreutz May 01 '20 at 20:07
  • You seem to have a `FeignErrorDecoder`. Can you share the code please? – Michael Kreutz May 02 '20 at 09:47
  • hmmm.. I do not find the problem. usually this kind of error pops up if the underlying stream has already been consumed. So watch out for places where you called already `response.body()` before the `FeignErrorDecoder`. Maybe another decoder? – Michael Kreutz May 02 '20 at 12:46
  • @MichaelKreutz i don't have any other decoder, this is the only place i interact with the response from the feignService(calee) in case it throws an exception. – Sebilix May 02 '20 at 13:47
  • I think I would have to debug it to find out more.. If you can share the code, I will look at it.. – Michael Kreutz May 02 '20 at 14:01
  • Unfortunately the complete package structure is destroyed in the packed file - all files are in one folder. – Michael Kreutz May 02 '20 at 19:25
  • @MichaelKreutz open one of the 3 services normally, and after add the remaining two as follows (in IntelliJ) new-module from existing sources; after clean-install all of them – Sebilix May 03 '20 at 09:16
  • Could you somehow provide it in a way that the project structure is ok, e.g. sharing a github repo? – Michael Kreutz May 05 '20 at 18:59

0 Answers0