Spring-cloud-netflix: @PathVariable not working with feign

Created on 29 Feb 2016  路  13Comments  路  Source: spring-cloud/spring-cloud-netflix

When an interface has @PathVariable in it, feign fails to generate a client for it.

@FeignClient("HelloServer")
public interface ITestController {

    @RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
    String hello(@PathVariable String name);

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    String hello();

    @RequestMapping(value = "/randomJob", method = RequestMethod.GET)
    JobDto getRandomJob();
}

Client Code:

@SpringBootApplication
@RestController
@ComponentScan("client")
@EnableFeignClients
@EnableDiscoveryClient
public class ClientApplication {

    @Autowired
    ITestController client;

    @RequestMapping("/hello")
    public String hello() {
        return client.hello();
    }

    @RequestMapping("/hello/{name}")
    public String helloWithName(@PathVariable String name) {
        return client.hello(name);
    }

    @RequestMapping("/job")
    public JobDto randomJob() {
        return client.getRandomJob();
    }

    public static void main(String[] args) {
        SpringApplication.run(ClientApplication.class, args);
    }
}

Exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'clientApplication': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: client.ITestController client.ClientApplication.client; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'client.ITestController': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: PathVariable annotation was empty on param 0.
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839) ~[spring-context-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538) ~[spring-context-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766) [spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361) [spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191) [spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180) [spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at client.ClientApplication.main(ClientApplication.java:42) [main/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) [idea_rt.jar:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: client.ITestController client.ClientApplication.client; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'client.ITestController': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: PathVariable annotation was empty on param 0.
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:573) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 22 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'client.ITestController': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: PathVariable annotation was empty on param 0.
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:175) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:103) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1590) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:254) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1192) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 24 common frames omitted
Caused by: java.lang.IllegalStateException: PathVariable annotation was empty on param 0.
    at feign.Util.checkState(Util.java:108) ~[feign-core-8.1.1.jar:8.1.1]
    at org.springframework.cloud.netflix.feign.annotation.PathVariableParameterProcessor.processArgument(PathVariableParameterProcessor.java:49) ~[spring-cloud-netflix-core-1.1.0.M5.jar:1.1.0.M5]
    at org.springframework.cloud.netflix.feign.support.SpringMvcContract.processAnnotationsOnParameter(SpringMvcContract.java:169) ~[spring-cloud-netflix-core-1.1.0.M5.jar:1.1.0.M5]
    at feign.Contract$BaseContract.parseAndValidatateMetadata(Contract.java:75) ~[feign-core-8.1.1.jar:8.1.1]
    at feign.Contract$BaseContract.parseAndValidatateMetadata(Contract.java:49) ~[feign-core-8.1.1.jar:8.1.1]
    at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:137) ~[feign-core-8.1.1.jar:8.1.1]
    at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:55) ~[feign-core-8.1.1.jar:8.1.1]
    at feign.Feign$Builder.target(Feign.java:166) ~[feign-core-8.1.1.jar:8.1.1]
    at org.springframework.cloud.netflix.feign.FeignClientFactoryBean$DefaultTargeter.target(FeignClientFactoryBean.java:203) ~[spring-cloud-netflix-core-1.1.0.M5.jar:1.1.0.M5]
    at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.loadBalance(FeignClientFactoryBean.java:153) ~[spring-cloud-netflix-core-1.1.0.M5.jar:1.1.0.M5]
    at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:173) ~[spring-cloud-netflix-core-1.1.0.M5.jar:1.1.0.M5]
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 32 common frames omitted

Most helpful comment

Hello,

not tested, but i believe you have to supply the path variable's name like this in the annotation:

@RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
String hello(@PathVariable("name") String name);

All 13 comments

Hello,

not tested, but i believe you have to supply the path variable's name like this in the annotation:

@RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
String hello(@PathVariable("name") String name);

@ezraroi, @Koizumi85's suggestion is a good workaround. See #841 for work being done to fix this.

Looks like it is solving the issue.

I also noticed that if I have @RequestMapping annotation on the interface, the generated client is ignoring it.

@FeignClient("HelloServer")
@RequestMapping(value = "/api")
public interface ITestController {

    @RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
    String hello(@PathVariable(value = "name") String name);

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    String hello();

    @RequestMapping(value = "/randomJob", method = RequestMethod.GET)
    JobDto getRandomJob();
}

The generated client will not add \api to the requests it is making

Putting @RequestMapping on a controller is not currently supported. It causes spring mvc to try and use the feign client as an actual controller.

@spencergibb thanks for the quick response

Is there already a request issue to support it?

To support what?

Let Mvc detect that a @RequestMapping on (a extended) interface is intensionally used by a @FeignClientto reuse the api contract and not meant to be as own controller instance. Normally thta shall be the case if there is no implementation available for this interface.
Its related with https://jira.spring.io/browse/SPR-11055 and https://github.com/spring-projects/spring-framework/pull/976

The original issue here seems to be the non-defaulted parameter name. This is addressed by #835 .

To support Putting @RequestMapping on a Controller (or some parent in
inheritance hierarchy using Scoped interfaces)

@cforce that is not this issue.

@spencergibb what is the mechanism for feignclient annotation to work

// you need to add value in path variable as shown below.
@FeignClient("HelloServer")
public interface ITestController {

@RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
String hello(@PathVariable(value="name") String name);

@RequestMapping(value = "/hello", method = RequestMethod.GET)
String hello();

@RequestMapping(value = "/randomJob", method = RequestMethod.GET)
JobDto getRandomJob();
Was this page helpful?
0 / 5 - 0 ratings