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
@Configurationclasses are being scanned —@ComponentScanis 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
finalclasses or overridefinalmethods. - Visibility — Methods must be
publicfor proxy-based AOP. - Wrong bean class —
BeanPostProcessors 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 stack —
RequestMappingHandlerMappingbuilds the routing table at startup. Enable DEBUG logging onorg.springframework.web.servlet.mvc.method.annotationto see the table. - Reactive stack —
RequestMappingHandlerMapping(a different class inorg.springframework.web.reactive.result.method.annotation). - Confirm the controller is actually picked up by component scanning (
@ComponentScanreach). - Confirm content negotiation: a method that produces
application/jsonwon't match a request withAccept: 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
RuntimeHintssystem inspring-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
RuntimeHintsRegistrarand reference it fromMETA-INF/spring/aot.factories. - For framework code, search for
*RuntimeHintsclasses near the missing API to see how the framework registers itself. - Build with
-Pspring.aot.print-reachability=truestyle 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
@TestPropertySourceproduce different cache keys — that's intentional. - A pile of unique cache keys (e.g., random
@TestPropertySourceper class) blows out the cache. Default cache size is 32 contexts.
Build problems
- Cannot find toolchain — Gradle toolchains will download missing JDKs unless
toolchainResolverPluginsis 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
-Xmxingradle.propertiesor pass-Dorg.gradle.jvmargs=-Xmx4gon the command line. - Stale daemons —
./gradlew --stopresets 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.