Notes for Spring

августа 17, 2026

  1. BeanFactory vs ApplicationContext: BeanFactory is lazy (never builds objects unless getBean()); ApplicationContext eagerly builds all singletons at startup.

  2. Almost everything (@Autowired, @Transactional, etc.) is implemented via BeanPostProcessors auto-registered only through ApplicationContext, not a raw BeanFactory.

  3. Business code should never call getBean(), because it is Service Locator anti-pattern it defeats DI, breaks unit-testability without a full container.

  4. getBean() might hand us a proxy, not a literal class - this is why self-invocation breaks @Transactional (calling through this skips the proxy entirely).

  5. Bean names can silently collide - last one registered wins by default (logged at INFO); disable via spring.main.allow-bean-definition-overriding=false.

  6. Nested classes get compound default names (outer.Inner), not just decapitalized simple names - this can be confirmed via @Qualifier("c") lookup.

  7. Constructor injection > setter injection for required deps - fails at construction, not later.

  8. Circular dependencies via constructor injection is an exception and problem is the design of code not the framework.

  9. @Bean methods calling each other directly inside @Configuration get CGLIB-intercpeted (routed through the container for singleton consistency) - but that path does not catch cycles the way parameter-based resolution does. Direct call inside method body is StackOverflowError; Parameter injection of the same cycle can detect spring bean cycle and give clean exception.

  10. The CGLIB interception only happens in @Configuration classes. Inside plain @Component classes, @Bean-to-@bean calls are ordinary Java method calls - no container routing, no singleton guarantee at all.

  11. static @Bean methods are never CGLIB-intercepted, even inside @Configuration - hard technical limit, CGLIB can only override non-static methods.

  12. @DependsOn forces construction order, no object reference needed. Deconstruction order is the exact reverse.

  13. @Lazy is a request, not a guarantee - overridden by anything eager needing the bean, via a real dependency or @DependsOn alone.

  14. @DependsOn != SmartLifecycle. Construction order says nothing about start/stop order for runtime behavior. getPhase() controls that - lower starts first, stops last.

  15. @PostConstruct runs while a singleton-creation lock is held. Real deadlock risk exists in concurrent scenarios. It is suggested that keep it simple only for configuration validations to let it finish fast.

  16. Full per-bean lifecycle order: constructor -> dependency injection -> aware callbacks -> postProccessBeforeInitialization -> @PostConstruct -> postProcessAfterInitialization -> bean live.

  17. Destroy order mirrors init: @PreDestroy -> DisposableBean.destroy() -> custom destroy method.

  18. Ambiguous single-value injection throws NoUniqueBeanDefinitionException unless resolved.

  19. @Primary: Binary flag, exactly-one-wins. Two @Primary beans still throws - no ranking exists between them.

  20. @Fallback: Excludes candidates; Resolves only if exactly one non-fallback bean remains. Multiple situations still throw, unless they are all not required.

  21. @Primary: checked before @Fallback; If @Primary resolves it, @Fallback elsewhere is never consulted.

  22. No numeric priority tier system exists for single-value autowiring. Beyond one level of preference: List<T> injection + @Order on bean + manual logic to resolve.

  23. @Qualifier("name"): explicit override, beats @Primary. Should be added on bean as well as on injection point.

  24. Custom qualifier annotations: nest @Qualifier inside custom annotation.

  25. Generics as implicit qualifiers: Store<String> vs Store<Integer> disambiguate automatically via type parameter - no annotation needed, as longa s the specific type is preserved at the source.

  26. Multi-attribute qualifiers: one annotation, multiple attributes, all must match (@RoutingRule(region=..., urgent=...)).

  27. autowireCandidate = false on Bean blocks a bean from ALL type/qualifier resolution. defaultCandidate = false blocks only plain by-type resolution, still allows targeted @Qualifier injection. Neither flag has a @Component-level annotation equivalent - only @Bean method attributes. Verified via search.

  28. Name fallback matching: with nothing else present, Spring matches injection point variable/parameter name against bean names.

  29. @Resource vs @Autowired: @Resource = name-first, type-as-fallback. @Autowired = type-first, name-as-last-resort. Opposite priority direction.

  30. Self-injection: real, supported, lowest-priority fallback. Rides on the flag that should be configured via spring.main.allow-circular-references=true, but is not suggested.

  31. Constructor selection rules: single constructor -> always used. Multiple constructors -> exactly one may be required=true (or unmarked by default); If more than one is @Autowired, ALL must be required=false. Mixing required + optional across multiple annotated constructors is a hard startup error.

  32. @Order: only reorders items inside a collection injection point.

  33. Post-procesors can't use @Autowired/ @Value / @Resource on themselves - those annotations are implemented BY post-processors, which instantiate too early in the pipeline for that machinery to apply to them.

  34. singleton / prototype universal; request / session / application / webscoket web-only.

  35. Injecting prototype into a singleton field freezes it at one instance forever.

  36. @Lookup: abstract method, CGLIB-generated subclass provides a real implementation returning a fresh, fully-Spring-wired intance every call. Different from new - new skips DI entirely. Reuqires non-final class/method; doesn't work with @Bean factory methods (container doesn't control instantiation there). Use case: fresh instance needed but must also receive its own injected dependencies - new alone can't wire it, @Lookup can.

  37. Scoped proxies: Same, core problem, one level more serious - injecting request / session into singleton. Spring injects a stand-in proxy fetching "the real one for right now" on every call.

  38. @RequestScope = @Scope("request") + proxyMode = TARGET_CLASS pre-enabled. Plain @Scope("request") has no proxy by default - breaks at startup if injected into a singleton.

  39. proxyMode: NO (no stand-in), TARGET_CLASS (CGLIB subclass, works on almost any non-final class), INTERFACES (JDK dynamic proxy, needs an interface).

  40. @PostConstruct / @PreDestroy are the modern replacement for InitializingBean / DisposableBean.

  41. Destroy-method inference: close() method (or AutoCloseable / Closeable) get called automatically on shutdown - only for @Bean-declared beans, not @Component-scanned ones.

  42. registerShutdownHook() needed for non-web apps on ConfigrableApplicationContext.

  43. SmartLifecycle: separate from construction order entirely - controls actual start/stop after ALL beans are built, via getPhase().

  44. Thread safety: singleton fields set only during init get safe-publication guarantees automatically. Fields mutated after init need volatile/locking.

  45. BeanPostProcessor: operates on finished bean instances, two hooks around init callbacks. This is how @Autowired / @Transactional proxying work.

  46. BeanFactoryPostProcessor: operates on bean definitions before anything is built. @Value("${...}") resolution is powered by one (PropertySourcesPlaceholderConfigurer).

  47. Post-processor @Bean methods must be static - must exist before the normal pipeline, including their own enclosing @Configuration instance, is ready. Deeper reason: CGLIB can't intercept static methods anyway, so static also sidesteps unwanted interception.

  48. FactoryBean: hands complex construction logic to Spring via getObject(). getBean("name") -> product. getBean("&name") -> the factory itself.

  49. Constructor, setter, arbitrary multi-arg method, and field injection all supported; can be mixed.

  50. required = false: fields/setters silently skip if unsatisfiable. Constructor args are different - use Optional<T> or @Nullable to express genuine optionality.

  51. BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, MessageSource are auto-resolvable with zero setup, for both @Autowired and @Resource.

  52. @Service / @Repository / @Controller are @Component via meta-annotation.

  53. @Repository has one real behavioral extra: automatic exception translation.

  54. Filters (ANNOTATION / ASSIGNABLE / REGEX / ASPECTJ / CUSTOM) exist for scanning in/out classes beyond the default stereotype set - rare, good to know exists.

  55. @Lazy can be placed directly on an @Autowired injection point, not just the bean itself - gives a lazy-resolution proxy for that specific injection.

  56. There exists other annotations for dependency injection called @Inject / @Named that other DI farmework implement.

  57. proxyBeanMethods=false No CGLIB subclass, @Bean methods are just plain factory methods, no interception, no singleton guarantee on direct calls. @Configuration(proxyBeanMethods = false) can be done on config level.

  58. AnnotationConfigApplicationContext = register() (explicit classes) + scan() (package-wide @Component detection) + refresh() (actually build). SpringApplication.run() does all three automatically.

  59. @Configuration classes are meta-annotated with @Component - this is why they get auto-detected by component scanning with zero explicit registration, same as any other @Component.

  60. Declaring a @Bean method's return type as an interface instead of the concrete class limits the container's type knowledge until that bean is actually instantiated - @Autowired ConcreteType elsewhere can become declaration-order-dependent: works or fails depending on whether the interface-returning bean happened to be built first. Prefer the most specific return type your injection points actually need.

  61. Default methods on interfaces can carry @Bean - lets you compose bean definitions via interface inheritance (@Configuration class implements BaseConfig), including from multiple interfaces at once (Java allows multiple interface implementation, unlike class inheritance). Rare, but real.

  62. @Bean(destroyMethod = "") explicitly disables destroy-method inference - needed for externally-managed resources (doc's own example: a JNDI-provided DataSource, where Spring auto-closing it would be wrong since its lifecycle isn't Spring's to manage).

  63. @Bean({"name1", "name2", "name3"}) - a String[], not just a single name - gives one bean multiple aliases simultaneously. All names resolve to the same singleton instance.

  64. @Description("...") - pure metadata, no runtime behavior, useful mainly for monitoring/JMX exposure.

  65. Inter-bean dependencies expressed by one @Bean method calling another only work inside @Configuration classes - not supported in plain @Component classes, even though such a call may compile and appear to run.

  66. @Configuration classes get CGLIB-subclassed at startup, which is why they must not be final. Constructors - including @Autowired constructor injection - remain fully supported on @Configuration classes themselves; the restriction only concerns subclassing, not construction.

  67. The actual CGLIB mechanism, stated directly: the generated subclass overrides every @Bean method to check the container's singleton cache first, and only executes the real method body if nothing's cached yet. This is why calling the same @Bean method multiple times in source code still produces exactly one singleton instance.

  68. CGLIB is repackaged directly inside spring-core (org.springframework.cglib) - no separate dependency needed to use it.

  69. To avoid CGLIB entirely: move @Bean methods to a plain @Component class, or use @Configuration(proxyBeanMethods = false). Either way, @Bean-to-@Bean calls stop being intercepted, and all inter-bean dependencies must be expressed as constructor/method parameters instead.

  70. @Import(OtherConfig.class) pulls in another @Configuration class's beans without needing to list it explicitly at context startup - lets you build a tree of configs from one entry point.

  71. Cross-config-class dependencies: prefer method-parameter injection (works across @Configuration classes automatically). @Autowired fields directly on a @Configuration class are also valid (the config class is a bean itself), but the doc recommends parameter injection as the default, warning that field-injected dependencies on @Configuration classes risk unexpected early initialization.

  72. Accessing other locally-defined beans from inside a @PostConstruct method on the same @Configuration class is a real, documented anti-pattern - it's a circular reference in disguise (@PostConstruct requires the class instance to be fully constructed; the @Bean method call requires the class instance too), and triggers BeanCurrentlyInCreationException under Boot 2.6+'s circular-reference rejection.

  73. Background initialization (6.2+): @Bean(bootstrap = BACKGROUND) allows a bean's construction to run on a separate thread during startup - but only if a bean named bootstrapExecutor (type Executor) is also registered; otherwise the marker is silently ignored. Non-lazy dependents still block and wait for it; only @Lazy consumers actually benefit from the concurrency.

  74. @Conditional is the general mechanism underlying @Profile - @Profile is just a pre-built Condition implementation checking active environment profiles. You can write your own Condition class for arbitrary custom logic.

  75. A @Conditional on an outer @Configuration class does NOT automatically apply to a nested @Configuration class if that nested class gets discovered independently (e.g. via component scanning finding it on its own) rather than through the outer class's own processing or @Import - must be redeclared on the nested class in that case.

  76. @Import's real-world purpose is the exact mechanism behind Spring's own @Enable* annotations (@EnableScheduling, @EnableCaching, @EnableAsync, etc.) - each is just @Import wearing a friendlier name, pulling in infrastructure beans from a package the app never scans.

  77. Isolating @Import's actual effect requires a genuinely unscanned package - a nested class inside the main app's own package gets picked up by component scanning regardless of @Import (since @Configuration is @Component-scannable), so a same-package test proves nothing. Working correctly once ExternalConfig/InternalConfig were placed in truly separate packages.

  78. @PostConstruct : using an ALREADY-INJECTED field inside @PostConstruct is completely safe and common (DI finishes before @PostConstruct runs). The danger is narrower than "never touch beans" - specifically getBean() for something possibly still mid-construction, or a local @Bean method call on the same @Configuration class.

  79. Reconciled the earlier "deadlock risk" claim with Boot's clean cycle-rejection: same root cause (needing something to be fully done while not yet done), different symptom by context. Real concurrent/multi-threaded scenario = genuine lock contention, possible hang. Single-threaded startup = Boot's cycle detector catches it via static graph analysis before any blocking occurs, producing a clean rejection instead.

  80. Three legitimate @PostConstruct use cases, all sharing the test "only touches already-injected fields, never asks the container for anything new": (1) validating injected dependencies and failing fast, (2) setup work using only already-injected fields, (3) one-time registration/side-effect using only what's already on hand.

  81. When @PostConstruct's safe-zone test fails: use @DependsOn (ordering) or ApplicationListener<ContextRefreshedEvent> (guaranteed-safe post-startup hook, runs after all singletons are fully built) instead.

  82. @Bean(bootstrap = BACKGROUND) vs @Lazy - @Lazy delays WHEN construction starts (only begins on first use, maybe never). BACKGROUND does not delay start at all - construction begins immediately, just on a separate thread, off the main bootstrap thread.

  83. A non-lazy consumer of a BACKGROUND bean still blocks and waits for it, despite the target being backgrounded. The intended real pattern combines both: mark the slow bean BACKGROUND, and mark its consumers @Lazy too, so neither construction start nor consumer-waiting is forced.

  84. Custom @Conditional fully working end-to-end, including reading the checked property from application.properties, not just -D JVM flags - because Condition.matches() reads through the Environment abstraction, unifying properties files, JVM system properties, OS env vars, and command-line args. Same exact mechanism @Profile is built on, just a custom property check instead of env.matchesProfiles(...).

  85. BeanRegistrar: imperative alternative to annotation-based bean registration. Implement BeanRegistrar, import it via @Import on a @Configuration class, and use plain Java (if/for/loops) inside register(BeanRegistry, Environment) to decide what gets registered - registry.registerBean(name, Type.class, spec -> ...) with a supplier lambda works like a @Bean method body, with scope/laziness/description configurable via the spec builder.

  86. BeanRegistrar's actual use case: when the NUMBER or SHAPE of beans to register genuinely depends on runtime logic a static annotation can't express (e.g. a variable number of beans driven by a configured list) - a targeted escape hatch, not a replacement for ordinary @Bean/@Component usage.

  87. @Conditional operates uniformly at both class-level (@Configuration/@Component - entire class skipped, including its @Imports, if condition fails) and method-level (@Bean - only that method skipped, rest of the config still processes). Not tied to which stereotype is involved.

  88. Profile expressions support ! (NOT), & (AND), | (OR) - e.g. @Profile("production & us-east"). Mixing & and | without parentheses is invalid (production & us-east | eu-central fails) - must disambiguate with parentheses: production & (us-east | eu-central).

  89. @Profile can be used as a meta-annotation to build composed annotations (e.g. custom @Production nested around @Profile("production")) - same trick as @Channel/@RoutingRule earlier, just applied to profiles.

  90. Modern Spring (@Configuration(enforceUniqueMethods = true), the default) now actively refuses to start if a @Configuration class has overloaded @Bean methods sharing a name - throws BeanDefinitionParsingException outright, telling you to use unique method names or explicitly set enforceUniqueMethods = false. The doc's described "only the first overload's condition matters" behavior only happens if you force the old permissive mode back on.

  91. With enforceUniqueMethods = false forced on, tested directly: the first-declared overload's @Profile governs whether the bean exists at all (not which one is "correct" for the active profile), but which overload's body actually executes is resolved separately, via ordinary greediest-satisfiable-candidate selection. Result: a bean can exist because profile A was active, while the actual returned value came from profile B's method body - a genuinely confusing mismatch between why the bean exists and what it actually returns.

  92. Activating a profile requires an explicit action - tagging beans with @Profile alone does nothing; with no profile ever activated, @Profile-tagged beans simply never register (NoSuchBeanDefinitionException if something depends on one).

  93. @Profile("default") does NOT mean "always active" - it means "active only when no profile has been explicitly activated at all." The moment any real profile is activated, default stops applying entirely; it is not a baseline layered underneath other profiles.

  94. Multiple profiles can be active simultaneously - not mutually exclusive (spring.profiles.active=profile1,profile2, or setActiveProfiles("p1","p2")).

  95. Environment.getProperty(...) walks an ordered list of PropertySources and returns the first match - not merged, fully overridden by whichever source is checked first. Framework-level default hierarchy (highest to lowest): ServletConfig params -> ServletContext params -> JNDI -> JVM system properties (-D) -> OS environment variables.

  96. -D JVM flag for spring.profiles.active overrides the same key set in application.properties - matches the precedence hierarchy prediction (JVM system properties outrank lower sources), tested directly by setting conflicting values in both places and observing only the -D-specified profile's bean execute.

  97. @PropertySource("classpath:...") inserts a properties file into the same PropertySource search chain used by Environment/@Value - not a separate mechanism. The resource path itself can contain a ${...} placeholder (with optional :default fallback), resolved against whatever property sources are already registered before this annotation processes; unresolvable with no default throws IllegalArgumentException, consistent with @Value's strict-by-default behavior.

  98. Load-time weaving modifies actual class bytecode at JVM class-loading time - a lower-level mechanism than Spring's runtime proxies (CGLIB/JDK dynamic proxy). Enabled via @EnableLoadTimeWeaving, requires a JVM agent to actually function. Mainly relevant to JPA entity class transformation (lazy-loading field interception); rarely configured directly in application code.

  99. MessageSource provides i18n message lookup. Requires a bean named exactly messageSource - if no bean has that exact name, ApplicationContext silently falls back to an empty no-op implementation instead of failing, so a misnamed bean produces no error, just silent non-functioning.

  100. MessageSource does not merge resource bundles with the same base name across the classpath - uses only the first one found, ignoring any duplicates. Relevant risk in multi-module projects where two dependencies might ship a bundle with the same base name.

  101. Locale-to-file mapping is standard Java ResourceBundle convention: basename_language_COUNTRY.properties (e.g. messages_en_GB.properties). A Locale object's own .toString() output matches this suffix exactly (Locale.UK.toString() returns "en_GB"), so the mapping can be checked directly rather than memorized.

  102. Locale fallback is a lookup chain, not a full-file merge: for a given locale, the most specific matching file is checked first; only keys missing from that file fall through to progressively less specific files (down to the base file with no suffix). Keys present in the specific file are used from there; keys absent from it are found in the more general file.

  103. getMessage(code, args, defaultMessage, locale) returns the default string if the code isn't found. getMessage(code, args, locale) (no default parameter) throws NoSuchMessageException instead if the code isn't found.

  104. The Spring event system (ApplicationEventPublisher / @EventListener) is a decoupled publish-subscribe mechanism: one bean publishes an event object, any number of independently-written listener beans can react, without the publisher knowing who is listening. Adding a new reaction requires only a new listener class, no changes to the publisher.

  105. Since Spring 4.2, any plain object can be published as an event - it does not need to extend ApplicationEvent. Spring wraps arbitrary objects automatically.

  106. Event listeners are synchronous by default: publishEvent(...) blocks until every listener finishes. This allows a listener to participate in the same transaction context as the publisher, when one is available - the safe default exists specifically to preserve that transactional consistency.

  107. @Async on a specific @EventListener method makes that listener run on a separate thread, so the publisher does not wait for it to finish. Tradeoff: an async listener loses transaction participation and cannot publish a follow-up event via a return value.

  108. @EventListener(condition = "...") accepts a SpEL expression evaluated against the event (accessible as #event or the parameter's declared name) - the listener method body only runs if the expression evaluates to true. Equivalent in effect to an if check inside the method body, but expressed declaratively on the annotation instead.

  109. @Order applies to @EventListener methods the same way it applies to collection injection - lower number runs first among multiple synchronous listeners for the same event. Ordering guarantees do not meaningfully apply to @Async listeners, since they run whenever their own thread gets scheduled, not in a guaranteed sequence relative to others.

  110. An @EventListener method can return a new event object instead of void; Spring automatically publishes the returned object as a new event, without needing to manually inject and call ApplicationEventPublisher. Returning null means nothing gets published. This return-based chaining is not supported for @Async listener methods.

  111. Resource is a unified abstraction over loading a file regardless of where it actually lives (classpath, filesystem, URL) - one consistent API instead of different code paths per source type. ApplicationContext implements ResourceLoader, so it can resolve Resource objects directly, and @Value on a Resource-typed field/parameter with a string like "classpath:..." gets auto-converted into the actual Resource object.

  112. @PropertySource("classpath:...") uses this same Resource/prefix mechanism under the hood - the classpath: prefix seen there is the general resource-location convention, not something specific to property files.

  113. ApplicationStartup records timestamped internal steps during container startup (package scanning, bean instantiation, post-processing, event handling) for performance diagnostics. It is a no-op by default, collecting nothing unless an actual implementation is explicitly configured on the context before startup. Explicitly scoped to the container's own internal phases - not a replacement for general-purpose profilers or metrics libraries.

  114. Resource extends InputStreamSource, which just contributes getInputStream(). Each call is expected to return a fresh stream that can be read again from the start - the one exception is when isOpen() returns true (only for InputStreamResource), meaning the stream is already open, can only be read once, and must then be closed.

  115. Resource string prefixes decide the concrete implementation created: classpath: -> ClassPathResource, file: -> FileSystemResource, https:/other URL schemes -> UrlResource. No prefix at all defers to whatever the surrounding context type considers its default.

  116. ClassPathResource cannot always resolve to a real java.io.File - only works if the resource happens to be on an actual filesystem (e.g. during local dev, unpacked). Once the app is packaged into a jar, a classpath resource lives inside the jar, not as a standalone file, so getFile() fails in that case while getInputStream() still always works. Preferring getInputStream() over getFile() is the safer default for code that must work identically from an IDE and from a packaged jar.

  117. PathResource is a NIO java.nio.file.Path-based alternative to FileSystemResource, same purpose, different underlying API. ServletContextResource is web-app-specific, relative to a deployed web app's root - less relevant for standalone Boot jars. InputStreamResource/ByteArrayResource wrap data that didn't originate from a file at all (an already-open stream, or raw bytes already in memory), letting that data still be passed around using the uniform Resource type.

  118. org.springframework.validation.Validator is a two-method interface: supports(Class<?>) decides whether this validator applies to a given type; validate(Object, Errors) performs the checks. It has zero dependency on Spring MVC, HttpServletRequest, or any web type — it can be instantiated and invoked directly (new SomeValidator().validate(obj, errors)) with no ApplicationContext running.

  119. Errors (typically a BeanPropertyBindingResult instance in standalone use) is an accumulator, not an exception. rejectValue(field, errorCode) records a failure without halting execution — multiple independent checks in one validate() call can all fail and all get reported in a single pass, unlike a design that throws on the first bad field.

  120. The errorCode string passed to rejectValue (e.g. "name.empty") is a lookup key for a MessageSource, not a message to display. This is what decouples validation logic from language — the same validator produces the same codes regardless of locale; only the later message-resolution step is locale-aware.

  121. ValidationUtils is a static helper that wraps common rejectValue patterns (rejectIfEmpty, rejectIfEmptyOrWhitespace) so you don't hand-write null/empty checks repeatedly.

  122. Casting inside validate(Object obj, Errors e) (Person p = (Person) obj) is safe only if the caller actually checked supports() first — the interface itself doesn't enforce this. Calling validate() with a type supports() would have rejected produces a ClassCastException at runtime, not a compile error.

  123. For validating nested objects (e.g. a Customer containing an Address), delegate to a dedicated Validator for the nested type rather than inlining every field check into one giant validator. Wrap the delegated call in errors.pushNestedPath("address") / errors.popNestedPath() so field errors from the nested validator get correctly prefixed ("address.street" instead of just "street"). Always do this in a try/finally — an early return between push and pop leaves the path prefix stuck for every subsequent rejectValue call in that validation run.

  124. As of Spring Framework 6.1, Validator gained a default method validateObject(Object) for one-off, non-binding validation — it returns an Errors you can inspect (hasErrors()) or convert to an exception via .failOnError(SomeException::new). This is newer and less universally documented than the core two-method interface, so confirm the exact signature against your actual Spring version rather than assuming it's present.

  125. Validator.validateObject(Object) (6.1+) is backed by SimpleErrors, not BeanPropertyBindingResult — a lighter Errors implementation that does not support nested paths. Any validator that calls pushNestedPath()/popNestedPath() (i.e., any validator composing a nested validator, like the Customer/Address pattern) throws UnsupportedOperationException: SimpleErrors does not support nested paths if invoked via validateObject(). For validators that do nested delegation, construct new BeanPropertyBindingResult(target, "name") manually and call validate(target, errors) directly instead of using the validateObject() shortcut.

  126. This distinction is not mentioned in the reference docs' prose — it only surfaces as a runtime exception, and only when the validator under test actually exercises nested-path support. A validator with no nested delegation will never reveal this limitation, which makes it an easy trap to hit later on an object graph that grows nested validators after validateObject() was already in use.

Back to Blog

Comments

Rajan Gupta
авг. 19, 2026 1:32 PM

Wow! I didn't know many of these things specified in here. Thank you so much! <3

Konstantine Vashalomidze
авг. 19, 2026 1:33 PM

No problem. I am glad for helping.

Grandpa Rick
авг. 20, 2026 5:23 AM

Is this AI generated?

Konstantine Vashalomidze
авг. 20, 2026 5:23 AM

Yes, it is.

Konstantine Vashalomidze
авг. 26, 2026 11:44 AM

But with the help of myself.

Leave a Comment