Can't get Feign Client to work for a basic example

Rockoder picture Rockoder · Jul 21, 2016 · Viewed 33k times · Source

Can't get Feign Client to work. First tried with POST. Kept running into errors related to Encoder/Decoder saying type is not right. Then found an example on github to call simple GET API finally and decided to give it a shot. Still fails

On Github and online, I am seeing multiple versions of Feign Client Spring-Cloud, OpenFeign, Netflix.feign having different versions. Could anyone describe what's the best and stable Feign client one should use for production?

package com.paa.controllers;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient (name="test-service",url="https://www.reddit.com/r")
public interface GetFeignClient {

     @RequestMapping(method = RequestMethod.GET, value = "/java.json")
     public String posts();
}

Controller:

@RestController
@RequestMapping("/some/api")
public class TestWLCController {

  @Autowired
  private GetFeignClient getFeignClient;

  .. some stuff


    @RequestMapping(value="/postSomething",method = RequestMethod.POST)
    @ApiOperation(value = "Configures something",
            notes = "basic rest controller for testing feign")

    public ResponseEntity<SomeResponse> feignPost(
            UriComponentsBuilder builder,
            @ApiParam(name = "myRequest", 
            value = "request for configuring something", 
            required = true)
            @Valid @RequestBody SomeRequest someRequest) {

        String resp = null;
        try {
            resp = getFeignClient.posts();
        } catch (Exception er) {
            er.printStackTrace();
        }

    }
}

Application:

Tried all possible permutations of annotations thinking it would resolve AutoWire stuff but still fails

@Configuration
@ComponentScan
@EnableAutoConfiguration
//@EnableEurekaClient
@EnableFeignClients

//@SpringBootApplication
//@EnableFeignClients
//@EnableFeignClients(basePackages = {"com.paa.xenia.controllers", "com.paa.xenia.services"})
public class ServiceApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {

        return application.sources(XeniaServiceApplication.class);
    }

    public static void main(String[] args) {

        SpringApplication.run(ServiceApplication.class, args);
    }
}

2016-07-20 18:15:42.406[0;39m [31mERROR[0;39m [35m32749[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.s.boot.SpringApplication [0;39m [2m:[0;39m Application startup failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testWLCController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.paa.controllers.GetFeignClient com.paa.controllers.TestWLCController.gfClient; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.aa..controllers.GetFeignClient': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~[spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE] at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE] at com.paa.ServiceApplication.main(ServiceApplication.java:44) [bin/:na] Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.paa.controllers.GetFeignClient com.paa.controllers.TestWLCController.gfClient; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.paa.controllers.GetFeignClient': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:573) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE] ... 17 com.n frames omitted

Answer

Abhijit Sarkar picture Abhijit Sarkar · Dec 13, 2016

I'm not sure if you eventually figured it out yourself, but for the sake of others who might stumble across this thread, below is a working example of what you were trying to do. I'll first point out a few things that are incorrect, or at least, not desired in your code, then show my code that works.

  1. You should try not to use the url attribute. Instead, set up a list of servers using <feign client name>.ribbon.listOfServers in bootstrap.yml (or bootstrap.properties). This allows for client side load balancing because listOfServers can be a comma-separated list.
  2. Using Ribbon for HTTPS connections, you need to specify two things that you don't need for HTTP connections. The port, which is part of the listOfServers and <feign client name>.ribbon.IsSecure: true. Without the port, the connection is made to port 80 and without the IsSecure, HTTP is used.
  3. Testing using curl, I found that Reddit takes a very long time to respond. See this SO post for details of how to break down the total time taken by the request-response cycle.

    $ curl -v -H "User-Agent: Mozilla/5.0" -w "@curl-format.txt" -o /dev/null -s "https://www.reddit.com/r/java/top.json?count=1"
    { [2759 bytes data]
    * Connection #0 to host www.reddit.com left intact
    time_namelookup:     0.527
    time_connect:        0.577
    time_appconnect:     0.758
    time_pretransfer:    0.758
    time_redirect:       0.000
    time_starttransfer: 11.189
                      ----------
    time_total:         11.218
    

According the Netflix Wiki, the default read and connect timeouts are 3000 milliseconds, so you will always timeout unless you change those.

  1. You may have noticed that I specified a User-Agent header in the curl request. That's because Reddis seems to be very picky about that and if not specified, returns a HTTP 429 "Too many requests" most of the times. They don't return a Retry-After header in the response so there's no telling how long you need to wait before making another request.
  2. Spring Cloud Netflix as well as Netflix Feign is open source, so some (a lot of) patience and debugging skills come really handy.

"Talk is cheap. Show me the code." (Torvalds, Linus (2000-08-25)).

I generated a Gradle app using the excellent Spring Initializr site. Here's a snippet from the build.gradle file.

dependencies {
    compile('org.springframework.cloud:spring-cloud-starter-feign')
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.SR3"
    }
}

Feign client:

@FeignClient(name = "reddit")
public interface RedditClient {
    @RequestMapping(method = GET, value = "/r/java/top.json?count=1",
            headers = {USER_AGENT + "=Mozilla/5.0", ACCEPT + "=" + APPLICATION_JSON_VALUE})
    public String posts();
}

Boot Application:

@SpringBootApplication
@EnableFeignClients
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RestController
    static class DemoController {
        @Autowired
        private RedditClient redditClient;

        @GetMapping("/posts")
        public String posts() {
            return redditClient.posts();
        }
    }
}

bootstrap.yml:

reddit:
  ribbon:
    listOfServers: www.reddit.com:443
    ConnectTimeout: 20000
    ReadTimeout: 20000
    IsSecure: true
hystrix.command.default.execution:
  timeout.enabled: true
  isolation.thread.timeoutInMilliseconds: 50000

Integration test:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoApplicationTest {
    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void testGetPosts() {
        ResponseEntity<String> responseEntity = restTemplate.getForEntity("/posts", String.class);

        HttpStatus statusCode = responseEntity.getStatusCode();
        assertThat(String.format("Actual status code: %d, reason: %s.",
                statusCode.value(), statusCode.getReasonPhrase()),
                statusCode.is2xxSuccessful(), equalTo(true));
    }
}