Open-Source Wikis

/

Spring Framework

/

How to contribute

/

Debugging

spring-projects/spring-framework

Debugging

Common ways things go sideways in this codebase, and where to look.

Bean wiring problems

Symptoms: NoSuchBeanDefinitionException, UnsatisfiedDependencyException, BeanCurrentlyInCreationException.

Where to look:

  • Run with -Dlogging.level.org.springframework.beans.factory=DEBUG (or the equivalent JUL config) to see bean creation order.
  • Check if @Configuration classes are being scanned — @ComponentScan is sometimes rooted at the wrong package.
  • Circular dependencies — Spring normally tolerates them for setter injection but not constructor injection. The error message names the cycle.
  • Conditional configuration — If you're using Spring Boot's @Conditional* annotations, mismatched conditions can hide beans.

The relevant production code is in spring-beans/src/main/java/org/springframework/beans/factory/support/ — particularly AbstractAutowireCapableBeanFactory.java and DefaultListableBeanFactory.java. The bean lifecycle is the trickiest part of the framework; expect to read these classes more than once.

AOP / proxy issues

Symptoms: A @Transactional method is not transactional. A @Async method runs synchronously. A @Cacheable method is not cached.

Common causes:

  • Self-invocation — Calling this.transactionalMethod() from within the same class bypasses the proxy. Inject a self-reference, refactor to call the method through a different bean, or switch to AspectJ weaving.
  • Final classes/methods — CGLIB proxies can't subclass final classes or override final methods.
  • Visibility — Methods must be public for proxy-based AOP.
  • Wrong bean classBeanPostProcessors sometimes exclude bean types from proxying.

To inspect the proxy chain at runtime, cast a bean to org.springframework.aop.framework.Advised and inspect its advisors.

SpEL evaluation errors

Symptoms: SpelEvaluationException, surprising results from @Value("${...}") vs @Value("#{...}").

  • ${...} is property placeholder resolution (PropertySourcesPlaceholderConfigurer).
  • #{...} is SpEL evaluation.
  • They can be combined (#{${some.property}}) but order matters.

When stuck, the safest approach is to write a small test that builds a SpelExpressionParser directly and evaluates the expression in isolation.

Web request not matching a controller

Symptoms: 404 on a route you can plainly see in the code.

Where to look:

  • Servlet stackRequestMappingHandlerMapping builds the routing table at startup. Enable DEBUG logging on org.springframework.web.servlet.mvc.method.annotation to see the table.
  • Reactive stackRequestMappingHandlerMapping (a different class in org.springframework.web.reactive.result.method.annotation).
  • Confirm the controller is actually picked up by component scanning (@ComponentScan reach).
  • Confirm content negotiation: a method that produces application/json won't match a request with Accept: application/xml.

Ahead-of-time / native image failures

Symptoms: Reflection / serialization / proxy failure at runtime in a GraalVM native image, but the same code works on the JVM.

  • The framework's RuntimeHints system in spring-core/src/main/java/org/springframework/aot/hint/ is responsible for telling GraalVM what reflection, resources, proxies, and serialization to keep.
  • For your code, register a RuntimeHintsRegistrar and reference it from META-INF/spring/aot.factories.
  • For framework code, search for *RuntimeHints classes near the missing API to see how the framework registers itself.
  • Build with -Pspring.aot.print-reachability=true style flags during AOT processing for more verbose output.

Test context cache surprises

Symptoms: A test class is unexpectedly slow to start, or two tests interfere with each other.

  • The TestContext framework caches contexts keyed by configuration. If a test mutates the context (adds/removes beans, changes properties) without @DirtiesContext, later tests see the mutated cache entry.
  • Property tweaks via @TestPropertySource produce different cache keys — that's intentional.
  • A pile of unique cache keys (e.g., random @TestPropertySource per class) blows out the cache. Default cache size is 32 contexts.

Build problems

  • Cannot find toolchain — Gradle toolchains will download missing JDKs unless toolchainResolverPlugins is restricted. Check ~/.gradle/jdks/ and ~/.gradle/caches/jdks/.
  • Tests fail in CI but pass locally — Often a timing-dependent test. The CI matrix runs on Java 21 and Java 25; check matrix exclusions in .github/workflows/ci.yml.
  • OOM during build — Bump -Xmx in gradle.properties or pass -Dorg.gradle.jvmargs=-Xmx4g on the command line.
  • Stale daemons./gradlew --stop resets all daemons.

Useful debug toggles

Property Effect
-Dorg.springframework.trace=true (informal — varies by subsystem)
--debug to gradlew Verbose Gradle logs
--info to gradlew Less verbose alternative
-Pspring.profiles.active=… Activate profiles in tests that pick them up

For deeper investigation, a JVM-level debugger attached to :spring-<module>:test (with --debug-jvm) is usually the fastest path.

See also: testing for the test infrastructure and patterns and conventions for code-level idioms that affect debugging.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Debugging – Spring Framework wiki | Factory