java:S6809 conflicts with Spring discouraging circular references

Hey there,

there is already a thread about this, but according to the FAQ old Threads should not be revived but rather referenced.

As the original thread is unsolved and lacking decent reproducibility steps, I will try to get things correct here. :slight_smile:

Issue recap

Rule java:S6809 (Methods with Spring proxy should not be called via β€œthis”) suggest injecting the service into itself to make the calls instead of using this. The Problem is, that Spring discourages the circular references caused by this self injection.

How to reproduce

  • Go to start.spring.io and generate a blank project (use maven, as the commands below depend on maven as i don’t know the gradle commands) and language java. all other options should not matter. no additional dependencies needed
  • download project and open in IDE of choice
  • copy code below into DemoApplication.java below the existing class (or a new file, does not matter)
  • run ./mvnw spring-boot:run to see how spring fails with a error about cirular references (see below); also to ensure the project is built in case sonar needs that
  • add sonar plugin to the pom.xml in build > plugins
  • run ./mvnw sonar:sonar "-Dsonar.host.url=http://sonarqube" (adapt required sonar parameter as necessary for own instance)

you can replace./mvnw with mvn if you have it on PATH, to avoid downloading another copy of maven.

Sonar Plugin (for reference what I used)

<plugin>
	<groupId>org.sonarsource.scanner.maven</groupId>
	<artifactId>sonar-maven-plugin</artifactId>
	<version>5.0.0.4389</version>
</plugin>

Code to copy

// import jakarta.annotation.Resource;
// import org.springframework.scheduling.annotation.Async;
// import org.springframework.stereotype.Service;

@Service
class SonarBug {

    @Resource // as suggested by SonarQube; Spring does not like this.
    private SonarBug service;

    public void badForSonar() {
        async();
    }

    public void goodForSonar() {
        service.async();
    }

    @Async
    public void async() {
        // empty for demo purposes
    }
}

Spring Error

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

β”Œβ”€β”€->──┐
|  sonarBug
└──<-β”€β”€β”˜


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

Our Sonar Server

Community Build
v25.3.0.104237
Standard Experience
via Docker

1 Like

In one project we have used the code below to get the needed repository (aka the service) at runtime / during first use.

This is used in repository extension classes. I am not sure, if this kind of solution is a proper alternative to suggesting this in most cases.

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.data.repository.CrudRepository;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;

public abstract class AbstractRepositoryExt<T, R extends CrudRepository<T, Long>>
		implements BeanFactoryAware {

	private BeanFactory beanFactory;
	private final Class<R> repositoryClass;
	private R repository;

	protected AbstractRepositoryExt(Class<R> repositoryClass) {
		this.repositoryClass = repositoryClass;
	}

	@Override
	public void setBeanFactory(@NonNull BeanFactory beanFactory)
			throws BeansException {
		this.beanFactory = beanFactory;
	}

	protected synchronized R getRepository() {
		Assert.notNull(beanFactory, "beanFactory not set");
		if (repository == null) {
			repository = beanFactory.getBean(repositoryClass);
			Assert.notNull(repository, () -> "repository bean with type "
					+ repositoryClass.getName() + " not found");
		}
		return repository;
	}
}

And another minor finding: In the documentation of the issue, the code is incomplete. :smiley:

Line 41 should be

private AsyncNotificationProcessor asyncNotificationProcessor;

Thank you for the report! I filed SONARJAVA-5399 for the issue with the circular dependency and SONARJAVA-5401 for the typo.

I think in this case we will need to allow circular dependency. Did you try to resolve it with @Lazy annotation on the service?

Fyi, I used @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) to resolve the error about circular dependencies with @Cacheable, which shares the same issue.