What does ConditionContext give a Condition access to, and what can you realistically inspect from it?
answer
- getRegistry / getBeanFactory(nullable) / getEnvironment / getResourceLoader / getClassLoader
- registry = definitions so far; beanFactory may be null early
- ClassUtils.isPresent, not Class.forName
- Environment for property gating
- Registry is a moving target -> phase control for 'missing bean'
basics
~10 sConditionContext is the first argument to matches(). It exposes the BeanDefinitionRegistry, the (possibly null) ConfigurableListableBeanFactory, the Environment, the ResourceLoader, and the ClassLoader — enough to check properties, classpath, and already-registered bean definitions.
solid answer
~40 sConditionContext is passed to Condition.matches() and is the condition's window into the container as it's being built. It offers: getRegistry() → the BeanDefinitionRegistry, the most reliable way to ask 'is a definition for X already present?'; getBeanFactory() → the ConfigurableListableBeanFactory (can be null in early phases, so guard it); getEnvironment() → property and profile lookups; getResourceLoader() → to probe for resources; and getClassLoader() → to test class presence without forcing initialization. With these you implement things like 'match only if property foo=bar' (Environment), 'match only if class X is on the classpath' (ClassLoader/ClassUtils.isPresent), or 'match only if no bean of type Y is defined yet' (registry/beanFactory). Because conditions run during configuration parsing, the registry reflects only definitions processed so far — ordering matters, which is why 'missing bean' style conditions need careful phase control.
code
java · 12 linespublic class OnJacksonPresentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// classpath probe without initializing / throwing
boolean jackson = ClassUtils.isPresent(
"com.fasterxml.jackson.databind.ObjectMapper",
context.getClassLoader());
// registry probe: only if no custom mapper already defined
boolean noCustom = !context.getRegistry().containsBeanDefinition("objectMapper");
return jackson && noCustom;
}
}go deeper
List that it exposes environment, registry/bean factory, resource loader and class loader.
Explain each accessor, nullability of beanFactory/classLoader, and typical uses (property, classpath, existing-bean checks).
Discuss registry-vs-beanFactory ordering and why type-based 'missing bean' checks need phase control; use ClassUtils.isPresent.
Connect the moving-target registry to the design of ConfigurationCondition and how Boot orders auto-config back-off correctly.
**What ConditionContext is.** `org.springframework.context.annotation.ConditionContext` is the context object handed to `Condition.matches(context, metadata)`. It abstracts the still-being-assembled application context so a condition can make an informed decision. Its methods: - **`getRegistry()` → `BeanDefinitionRegistry`** — always available. Lets you call `containsBeanDefinition(name)` or `getBeanDefinitionNames()`. This is the safest way to reason about what's been registered so far, because it works even before the bean factory is fully usable. - **`getBeanFactory()` → `ConfigurableListableBeanFactory` (nullable)** — richer queries like `getBeanNamesForType(...)`, but may be `null` depending on when the condition runs (e.g. during `parseConfiguration` phase). Always null-check. - **`getEnvironment()` → `Environment`** — read properties (`getProperty`), the standard way to gate on configuration. (Profile checks technically go through the environment too, but `@Profile` is the dedicated mechanism owned by a sibling topic.) - **`getResourceLoader()` → `ResourceLoader`** — resolve/probe resources (e.g. a file or classpath resource must exist). - **`getClassLoader()` → `ClassLoader` (nullable)** — test class availability, typically via `org.springframework.util.ClassUtils.isPresent(name, classLoader)`, which returns false instead of throwing if the class or its dependencies are absent. This is exactly how `@ConditionalOnClass` avoids `NoClassDefFoundError`. **Why the registry-vs-beanFactory distinction matters.** Conditions run inside `ConfigurationClassPostProcessor` while definitions are still being discovered. A definition your condition cares about may not have been parsed yet. `getRegistry()` reflects the current set of *definitions*; the bean factory may not yet expose everything by type. This ordering sensitivity is the reason 'is a bean of type X already present?' conditions (like Boot's `@ConditionalOnMissingBean`) implement `ConfigurationCondition` and request the `REGISTER_BEAN` phase so they run after all user config has been parsed. **AnnotatedTypeMetadata (the second arg, for contrast).** ConditionContext is about the *container*; `AnnotatedTypeMetadata` is about the *annotated element* — it lets a composed condition read the attributes of the annotation that triggered it (e.g. which class name `@ConditionalOnClass` was given). Real conditions usually use both. **Gotchas.** - Don't force class initialization to test presence — use `ClassUtils.isPresent`, not `Class.forName(name)` (the latter initializes and can throw). - Treat `getBeanFactory()` and `getClassLoader()` as potentially null. - Remember the registry is a moving target during parsing; don't assume completeness unless you've controlled the phase. **When to use each.** Property-driven gating → Environment; optional-dependency/starter gating → ClassLoader; back-off / default-bean gating → registry + phase control; resource-presence gating → ResourceLoader.
- Why prefer ClassUtils.isPresent over Class.forName in a condition?isPresent catches ClassNotFoundException/NoClassDefFoundError and returns false without initializing the class, so a missing optional dependency degrades gracefully; Class.forName initializes the class and can throw or trigger side effects.
- Why might context.getBeanFactory() be null?Conditions can run in early configuration-parsing phases where the ConfigurableListableBeanFactory isn't yet exposed to the condition; you should null-check and, for type-based queries that need the full factory, use ConfigurationCondition with the REGISTER_BEAN phase.
saying these in an interview costs you the question
- Assuming getBeanFactory() and getClassLoader() are always non-null
- Using Class.forName to test classpath presence (initializes and can throw)
- Believing the registry reflects the final, complete set of definitions regardless of phase
- Confusing ConditionContext (about the container) with AnnotatedTypeMetadata (about the annotated element)