SpringBoot - BeanDefinitionOverrideException: Invalid bean definition
Asked Answered
C

9

107

I am trying to setup DynamoDB locally with Spring Boot. Initially I got the setup working and was able to write/save to DynamoDB via a repository. From that point I added more classes to build my application. Now when I try to start my application, I get the following exception:

org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'agentRepository' defined in null: Cannot register bean definition [Root bean: class [org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] for bean 'agentRepository': There is already [Root bean: class [org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.

I have searched SO and internet extensively but there were no any useful solution to this. The error message is misleading as well.

My project is of the following hierarchy

ai.test.as
  - as
      - agent
          - business
          - intent
          - exception
          - Agent.java
          - AgentDTO.java
          - AgentRespository.java
          - AgentController.java
          - AgentService.java
          - AgentServiceImpl.java
  - config
     - DynamoDBConfig.java

DynamoDBConfig.java

package ai.test.as.config;

import ai.test.as.agent.AgentRepository;
import ai.test.as.agent.intent.template.TemplateRepository;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableDynamoDBRepositories(basePackageClasses = {AgentRepository.class})
public class DynamoDBConfig
{
    @Value("${aws.dynamodb.endpoint}")
    private String dynamoDBEndpoint;

    @Value("${aws.auth.accesskey}")
    private String awsAccessKey;

    @Value("${aws.auth.secretkey}")
    private String awsSecretKey;

    @Bean
    public AmazonDynamoDB amazonDynamoDB()
    {
        AmazonDynamoDB dynamoDB = new AmazonDynamoDBClient(getAwsCredentials());
        dynamoDB.setEndpoint(dynamoDBEndpoint);

        return dynamoDB;
    }

    @Bean
    public AWSCredentials getAwsCredentials()
    {
        return new BasicAWSCredentials(awsAccessKey, awsSecretKey);
    }
}

AgentRepository.java

package ai.test.as.agent;

import ai.test.as.agent.Agent;
import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import org.springframework.data.repository.CrudRepository;

@EnableScan
public interface AgentRepository extends CrudRepository<Agent, String>
{
}

AgentController.java (Where AgentRepository is used)

@RestController
@RequestMapping(value = "/v1/agents")
public class AgentController
{
    @Autowired
    private AgentRepository agentRepository;

    @RequestMapping(value = "/test", method = RequestMethod.POST)
    public void test()
    {
        Agent agent = new Agent();
        agent.setAgentNumber("123456");
        agent.setId(1);

        agentRepository.save(agent);
    }
}

Spring suggests the following: > The bean 'agentRepository', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.

What does null mean here? Is it because something wrong in my application config? Also how is it possible that it is already registered?

Please give me some pointers because I so confused about my next steps.

Cristionna answered 11/12, 2018 at 11:33 Comment(7)
Have you tried to annotate AgentRepository with @Component ?Mountfort
Previously when I tried I did not use @Component annotation to read write from DynamoDB. But nevertheless I tried now as you mentioned and it does not work.Cristionna
Regarding your question if the bean is already registered you can print all the bean in the context via using the method getBeanDefinitionNames() of ApplicationContext object. Via printing them in the console you can see which beans keys are used. There are several example in the web so I won t share it hereMountfort
Can you please add [@Repository] or [@Service] annotations to your AgentRepository class and try? and also your AgentRepository is an interface, is there any use case to use interface instead of classes.Carbrey
Hi, did you solve this? I just encountered the same problem!Musculature
Actually no because I had switch from using DynamoDB. When switched the DB the problem disappeared. But the hacky way to fix it is to add spring.main.allow-bean-definition-overriding=true as mentioned in the answers.Cristionna
Sadly any other answer address the fact about why this problem starts to happen. In the theory the best resolution could start identifying why/how you have two beans.Dehaven
L
177

Bean overriding has to be enabled since Spring Boot 2.1,

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1-Release-Notes

Bean Overriding

Bean overriding has been disabled by default to prevent a bean being accidentally overridden. If you are relying on overriding, you will need to set spring.main.allow-bean-definition-overriding to true.

Set

spring.main.allow-bean-definition-overriding=true

or yml,

spring:
   main:
     allow-bean-definition-overriding: true

to enable overriding again.

Edit,

Bean Overriding is based of the name of the bean not its type. e.g.

@Bean
public ClassA class(){
   return new ClassA();
}

@Bean
public ClassB class(){
   return new ClassB();
}

Will cause this error in > 2.1, by default bean names are taken from the method name. Renaming the method or adding the name attribute to the Bean annotation will be a valid fix.

Leontine answered 11/12, 2018 at 11:58 Comment(8)
Hey thank you for your answer. But may I know why I should override the bean? I am sure I am only creating only one bean of AgentRepository. So whats being overridden?Cristionna
Read that exception that is not what it is complianing about. It is complaining about the DynamoDBRepositoryFactoryBean bean. I assume it is already being registered via the boot application and @EnableDynamoDBRepositories(basePackageClasses = {AgentRepository.class}) isn't required. Also to whomever downvoted its common to explain why.Leontine
Opinion that it's dirty given it is a feature in Spring Framework, and widely used. Removing the EnableDynamoDBRepositories(basePackageClasses = {AgentRepository.class}) my resolve it but I don't have access to a Dynamo setup if I had to guess it may be triggering creation of the bean again but if it doesn't resolve it I'd raise an issue on and provide an example.Leontine
@DarrenForsythe FYI I tried removing @EnableDynamoDBRepositories and removed the overriding property too but the problem still persists. So I think the said annotation is needed regardless.Cristionna
I assume that's a third party lib given the package, I'd reach out to the maintainers with a quick example. Likely was relying on the overriding capabilitiesLeontine
@DarrenForsythe Forsythe how to tell spring to use that configuration you mentioned, e.g yamlCarleencarlen
@ShellScott that is dependant on if you're using application.yml or not. Spring Externalized configuration documentation can explain the many ways to pass configuration rpopertiesLeontine
I believe this is a very risky workaround that could lead to indeterministic behaviour. Better to resolve the underlying issue which is that Spring is trying to create these repositories twice. Most likely caused by misconfiguration or non-compatible versions of spring-data-dynamodb / spring-boot / spring-dataSesquicentennial
C
29

Enable bean overriding with such approach for example

@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")

or

@SpringBootApplication (properties = "spring.main.allow-bean-definition-overriding=true")
Carleencarlen answered 6/2, 2020 at 14:29 Comment(3)
Note that if its a Test bean your solution is pretty much the same, don't rely on bean naming. rename it, use primary if required to inject itLeontine
Can you explain why this was necessary?Capuano
Yeah this is basically not necessary for a test. If you want to not have this issue you need to make sure your SpringBootTest is not picking up by default the main Application context if you have a specific on for your tests.Pyrene
P
2

I think I had the same problem with MongoDB. At least the error message looked exactly the same and I also had just one repository for the MongoDB, something like this:

public interface MyClassMongoRepository extends MongoRepository<MyClass, Long> {
}

The problem had been caused by class MyClass which had been used in another database before. Spring silently created some JpaRepository before creating the MongoRepository. Both repositories had the same name which caused the conflict.

Solution was to make a copy of MyClass, move it into the package of the MongoRepository and remove any JPA-specific annotations.

Pretext answered 19/5, 2020 at 19:1 Comment(2)
Yeah that fixes the problem but it ties your code to the persistence provider (MongoDB) which defeats the purpose of JPA.Sesquicentennial
@AhmadAbdelghany, I am aware of that but I simply wanted to provide another option for developers that do not want to change a global option to fix al local problem.Pretext
D
2

I just stumbled across the same issue while trying to add a PostgreSQL database via spring-data-jdbc to an existing project wich was already using a MongoDB.

It seems like the problem was that the repositories for MongoDB and PostgreSQL were scanned by both modules (spring-mongo and spring-jdbc). They both try to create some beans and clash.

In my case the MongoDB repositories and the PostgreSQL repositories were in the same package.

The accepted answer solved the problem for me - but i kind of got a hint from this startup logs:

Finished Spring Data repository scanning in 319ms. Found 4 repository interfaces
Finished Spring Data repository scanning in 319ms. Found 5 repository interfaces

This is weird because i only have 1 repository for PostgreSQL and 4 for MongoDB.

I moved the PostgreSQL repository into a different package than the MongoDB repository and configured the base package of the PostgreSQL repositories to the new package. In my case:

@EnableJdbcRepositories(basePackageClasses = MyOnlyPostgreSQLRepository.class) // TODO: Use the real package or a dedicated base class

This solved the issue for me (no property set for bean overriding - which i prefer). The startups logs also show the correct amount of repositories now (1 and 4).

Dever answered 1/7, 2020 at 16:23 Comment(1)
yes, I can confirm that adding an extra @Configuration bean with @EnableJpaRepositories(basePackages = {"A.repository", "A.common.repository"}), solved the issue in my case (I had Repository objects defined both in my main project (package A.repository), and in an intermediate library referenced by that project (package A.common.repository))Isleana
S
2

I came across this same issue, the problem was that multiple repository factories were trying to register all repositories interfaces with them. In my case, it was JpaRepositoryFactoryBean & DynamoDBRepositoryFactoryBean. As mentioned in other answers, you could see that on the logs indicated by:

[INFO] Bootstrapping Spring Data DynamoDB repositories in DEFAULT mode.
[INFO] Finished Spring Data repository scanning in 64ms. Found 2 DynamoDB repository interfaces.
[INFO] Bootstrapping Spring Data JPA repositories in DEFAULT mode.

Solution is:

  1. Make sure you are using compatible versions of spring-data-dynamodb / spring-boot / spring-data by checking the compatibility matrix
  2. Make sure each repository is created only once. In my case, I had to add
@SpringBootApplication
@EnableAutoConfiguration(exclude = {
       DataSourceAutoConfiguration.class,
       DataSourceTransactionManagerAutoConfiguration.class,
       HibernateJpaAutoConfiguration.class})

The config may differ in different versions and depending on how many repositories you have in your application. It may be helpful to read about Multi-Repository-configuration

Sesquicentennial answered 24/6, 2021 at 9:23 Comment(0)
E
1

In my case was that 2 maven depenencies were defined with the same Bean in it. I found this when issuing a mvn dependency:tree for all my projects.

Engel answered 17/5, 2021 at 7:33 Comment(0)
Y
1

you can use spring.main.allow-bean-definition-overriding=true in application.properties file

or

use @SpringBootApplication (properties = "spring.main.allow-bean-definition-overriding=true")

Yearly answered 20/7, 2022 at 6:20 Comment(0)
C
0

This error message is indicating that you have multiple bean definitions with the same name defined in your application. In Spring framework, each bean must have a unique name within the application context.

To resolve this issue, you should rename one of the classes or its bean definition, so that each bean definition has a unique name. You can do this by using the "name" attribute in the @Component, @Repository, or @Service annotations.

Cubic answered 9/2, 2023 at 7:25 Comment(0)
M
0

I strongly recommend you debug the source code where the exception throw. As I know the exception throw from this function "org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition" in spring. I copy a slice of code below:

    @Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        }
        else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            if (logger.isInfoEnabled()) {
                logger.info("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        existingDefinition + "] with [" + beanDefinition + "]");
            }
        }
        else if (!beanDefinition.equals(existingDefinition)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                removeManualSingletonName(beanName);
            }
        }
        else {
            // Still in startup registration phase
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
    else if (isConfigurationFrozen()) {
        clearByTypeCache();
    }
}

in above code you may found the exception BeanDefinitionOverrideException is exactly the root cause. When I set a break point at

BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);

You may found out all your beans are save in beanDefinitionMap, and if two bean has the same name, and the second bean called this map.get(beanName), it will retrieve the bean with the same name generate in pre process. and thus will trigger the exception!!

So now what you need to do is to compare this two bean. and found where did it come from. If it comes from xml or some Configuration code. and these is a debug skill you can paste the override bean name, in this case is "agentRepository". But in my case the name is "org.springframework.cache.config.internalCacheAdvisor" like my exception shows like :

Description:The bean 'org.springframework.cache.config.internalCacheAdvisor', defined in class path resource [org/springframework/cache/annotation/ProxyCachingConfiguration.class], could not be registered. A bean with that name has already been defined and overriding is disabled. Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

and it also ask me to "setting spring.main.allow-bean-definition-overriding=true" .

So set a break point at the line of map.get() code, then I paste the bean name in breakpoint condition:

beanName.equals("org.springframework.cache.config.internalCacheAdvisor")

if you don't know how to use debug condition, you can checkout this : How to use conditions in breakpoints in idea?

then finally I found out this breakpoint will trigger twice but it should trigger only once, as the second time when it triggered, it will throw the BeanDefinitionOverrideException.

as the bean with the same name has generate the second time. In my case, what I found out the root cause is the bean announced in both xml and again in spring default Configuration Code. But your case may not the same. So you need to use this debug tech to found the root cause!!

Hope this will help you, and as I know, don't open the override=true setting. it will get you more trouble than you thought!

Maundy answered 6/2 at 3:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.