3

I am working through a tutorial creating a eureka server and eureka client. Prior to adding the feign client, the eureka client is able to register with the eureka server. After I added the feign maven dependency and created the annotations in the PriceServiceApplication.java and PriceClient.java, I seem to be be getting an "Caused by: java.lang.IllegalStateException: Only single-level inheritance supported: PriceClient". This is a multimodule SpringBoot application project, containing both eureka client (price service) and eureka server. I am using Spring Data Rest with the price service microservice ensuring I could afford not to add the controller and service class. I will add the full errors and code snippet below. Thanks in advance for your help. I will appreciate any advice how to get rid of this error and get the code to work.

2023-04-04 15:29:04.693 ERROR 12212 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'pricingServiceApplication': Unsatisfied dependency expressed through field 'priceRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.udacity.pricing.domain.price.PriceClient': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Only single-level inheritance supported: PriceClient
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:843) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at com.udacity.pricing.PricingServiceApplication.main(PricingServiceApplication.java:32) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.1.5.RELEASE.jar:2.1.5.RELEASE]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.udacity.pricing.domain.price.PriceClient': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Only single-level inheritance supported: PriceClient
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:178) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:101) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1674) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getObjectForBeanInstance(AbstractAutowireCapableBeanFactory.java:1249) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:257) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1471) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1428) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1211) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1168) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    ... 24 common frames omitted
Caused by: java.lang.IllegalStateException: Only single-level inheritance supported: PriceClient
    at feign.Util.checkState(Util.java:127) ~[feign-core-10.1.0.jar:na]
    at feign.Contract$BaseContract.parseAndValidatateMetadata(Contract.java:55) ~[feign-core-10.1.0.jar:na]
    at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:154) ~[feign-core-10.1.0.jar:na]
    at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:52) ~[feign-core-10.1.0.jar:na]
    at feign.Feign$Builder.target(Feign.java:251) ~[feign-core-10.1.0.jar:na]
    at org.springframework.cloud.openfeign.HystrixTargeter.target(HystrixTargeter.java:37) ~[spring-cloud-openfeign-core-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:271) ~[spring-cloud-openfeign-core-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:235) ~[spring-cloud-openfeign-core-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:171) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    ... 35 common frames omitted


Process finished with exit code 1

PricingServiceApplication.java

@EnableDiscoveryClient
@EnableFeignClients
public class PricingServiceApplication implements CommandLineRunner{

    @Autowired
    PriceRepository priceRepository;

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



    /**
     * Gets a random price to fill in for a given vehicle ID.
     * @return random price for a vehicle
     */
    private static BigDecimal randomPrice() {
        return new BigDecimal(ThreadLocalRandom.current().nextDouble(1, 5))
                .multiply(new BigDecimal(5000d)).setScale(2, RoundingMode.HALF_UP);
    }

    @Override
    public void run(String... args) throws Exception {

        /**
         * Holds {ID: Price} pairings (current implementation allows for 20 vehicles)
         */
        final Map<Long, Price> PRICES = LongStream
                .range(1, 20)
                .mapToObj(i -> new Price("USD", randomPrice(), i))
                .collect(Collectors.toMap(Price::getVehicleId, p -> p));

        for (Map.Entry<Long, Price> entry : PRICES.entrySet())
        {
            System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
        }

        priceRepository.saveAll(PRICES.values());
    }
}

PriceRepository.java

@RepositoryRestResource(collectionResourceRel = "vehicle-price", path = "vehicle-price")
public interface PriceRepository extends CrudRepository<Price, Long> {

     @RequestMapping(method = RequestMethod.GET, value = "/prices/{vehicleId}")
     List<Price> findPriceByVehicleId(@Param("vehicleId") Long vehicleId);
}

PriceClient.java

@FeignClient(name="pricing-service", url = "pricing-service")
public interface PriceClient extends PriceRepository{

}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/>
         <!-- lookup parent from com.udacity.pricing.repository -->
    </parent>
    <groupId>com.udacity</groupId>
    <artifactId>pricing-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>pricing-service</name>
    <description>Pricing Service</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.11</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.11</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>netflix-candidates</id>
            <name>Netflix Candidates</name>
            <url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

application.properties

server.port=8082

#configure base path for rest apis
spring.data.rest.base-path=/api/v1/services

#h2
#spring.h2.console.enabled=true
#spring.h2.console.path=/h2
#spring.datasource.url=jdbc:h2:mem:pricedata
#
#spring.datasource.driverClassName=org.h2.Driver
#spring.datasource.username=sa
#spring.datasource.password=password
#spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

#Eureka
spring.application.name=pricing-service

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.client.service-url.default-zone=http://localhost:8761/eureka/
instance.preferIpAddress=true

eureka.client.enabled=true
spring.cloud.config.enabled=false
eureka.instance.hostname=localhost

spring.cloud.loadbalancer.ribbon.enable=false

Othello
  • 55
  • 11

1 Answers1

2

Spring Cloud Feign does not support inheritance of more than one level. In the feign client classes, you are inheriting from some repository that itself inherits from CrudRepository, not to mention that CrudRepostory itself inherits from the Repository interface!

So basically you are using Feign the wrong way. You should create the abstract methods inside the Feign Client classes, and enable HyperMediaSupport in your application, so that it can parse HATEOAS-enabled JSON generated by Spring Data Rest.

Note that the feign client classes can inherit from other classes or interfaces, only if those classes or interfaces do not have parents.

This link might be of help.

Shayan Daneshvar
  • 297
  • 1
  • 10
  • 1
    Thanks for your explanation. If I run with your advice and declare the "findPriceByVehicleId" in the PriceClient class, how do I access the methods generated in the PriceRepository class since it is not annotated with @FeignClient? – Othello Apr 10 '23 at 14:33
  • I think you misunderstood how Feign works. The repository interfaces are for accessing the database only, and the methods you declare there, are implemented when you lunch your application. Feign, on the other hand, generates abstract methods so that you can call other services (External), without using RestTemplate or similar constructs. Feign makes calling external services look like you are working with an internal service. – Shayan Daneshvar Apr 11 '23 at 19:48
  • To put it differently, Feign methods have Spring MVC or JAX-RS annotations, and they are implemented using RestTemplates by Spring, repository interfaces are implemented using the Spring Data template methods (E.g. JDBCTemplate) and the way they are implemented is totally different. Spring Data Rest is an extra module that exposes repository methods as Restful APIs and is unrelated to Feign. None of them have any construct to support each other. – Shayan Daneshvar Apr 11 '23 at 19:55
  • So, you cannot inherit from the repository interfaces in Feign interfaces because Feign does not support multi-level inheritance. – Shayan Daneshvar Apr 11 '23 at 19:56
  • Plus, it doesn't make sense to inherit from repository interfaces in Feign, as Feign is used to call other services and your repository is already available in your service/application, and you do not need an HTTP call to your own service. (You did not indicate if you are using a shared library between services for this matter, so I assumed you don't have one - if you do, you should create a shared interface with @NoRepositoryBean on it) – Shayan Daneshvar Apr 11 '23 at 19:57
  • Hence, the reason you had no errors initially was that Spring Data was generating code for your Feign interface!!! and you were using it as a Repository, without making any HTTP calls. – Shayan Daneshvar Apr 11 '23 at 20:02
  • Thanks a lot for your explanation. Your insights corresponds to the research that I have also been doing online, you are spot on. I am new to Feign and Spring Data Rest just like you guessed above. The link you provided is proving helpful for an error I just encountered now. I am sure other folks would find your help invaluable – Othello Apr 12 '23 at 11:52