Prior to this commit, only the very first AsynchronousFileChannel#write
call in DataBufferUtils$WriteCompletionHandler#hookOnNext(DataBuffer)
was guarded against exceptions escaping synchronously, and even then
only via `catch (RuntimeException ex)`, per the original fix for
gh-36184.
While widening that guard to match the read side's `catch (Throwable
ex)` combined with `Exceptions.throwIfFatal(ex)` (see gh-37143), we
discovered that completed(Integer, Attachment) contains two more direct
`this.channel.write(...)` calls -- for continuing a partial write and
for advancing to the next ByteBuffer within the same DataBuffer's
iterator -- neither of which was guarded at all. Since completed() is
invoked by the channel's own completion callback, typically on a
different thread than the one that issued the original write, a
synchronous exception escaping either of those calls has no path back
to the FluxSink, and the resulting Flux hangs indefinitely, exactly as
described in gh-37143, for any write that receives a partial OS write
or spans multiple ByteBuffers.
To address that, this commit extracts a private write(ByteBuffer, long,
Attachment) helper that wraps the channel.write(...) call with a
try/catch block, routing any non-fatal Throwable -- via
Exceptions.throwIfFatal() -- to the existing failed(Throwable,
Attachment) handler. All three call sites (hookOnNext() and both
branches in completed()) now go through this helper, ensuring the
Flux always terminates with a proper error signal instead of hanging
silently, regardless of which write attempt fails or which thread it
fails on.
See gh-36184
See gh-37143
Closes gh-37145
Prior to this commit, DataBufferUtils$ReadCompletionHandler#read()
invoked AsynchronousFileChannel#read(ByteBuffer, long, Attachment,
CompletionHandler) without guarding against exceptions thrown directly
by that call. Although that method is documented to report failures
asynchronously via the supplied CompletionHandler, some platform-
specific implementations can instead throw synchronously – for
example, on Windows with JDK 25, when the JDK rejects a ByteBuffer
backed by a closeable shared memory Arena, as produced by Netty 4.2's
off-heap buffer allocation.
When such an exception is thrown from a recursive read() invocation
triggered from completed() – which happens once a resource requires
more than a single chunk – the exception has no path back to the
FluxSink: it escapes on whatever thread invoked the CompletionHandler,
and the resulting Flux never signals onError or onComplete. In
practice, this surfaced as an indefinite hang when serving a Resource
whose HTTP response is not a ZeroCopyHttpOutputMessage, since
ResourceHttpMessageWriter falls back to ResourceEncoder, which reads
the resource via DataBufferUtils.
To address that, this commit wraps the channel.read(...) call in a
try/catch block and routes any non-fatal Throwable to the existing
failed(Throwable, Attachment) handler, via Exceptions.throwIfFatal(),
mirroring the equivalent fix already applied to the write side for
gh-36184. This ensures the allocated DataBuffer is released and the
Flux always terminates with a proper error signal instead of leaking a
buffer or hanging silently.
See gh-36184
Closes gh-37143
A user reported confusion between two distinct uses of "wraps" in the
AOP proxy documentation for Bean Overrides: the sense in which a
Mockito spy wraps the original bean instance it was created from, and
the sense in which a Spring AOP proxy wraps the spy in the actual
object graph.
To address that, this commit adds two small diagrams to the general
"Bean Overrides and Spring AOP Proxies" section, illustrating, from a
caller's perspective, the shape of the bean for the
REPLACE/REPLACE_OR_CREATE strategy (no proxy at all) versus the WRAP
strategy (an AOP proxy still created, now wrapping the override
instance instead of the original bean). Both diagrams use the same
generic "override instance" label, since the section is not specific to
Mockito; a Mockito spy created by @MockitoSpyBean is mentioned only as
an example.
The accompanying text is revised to reserve "wraps" for the AOP proxy
relationship and to explicitly call out that a Mockito spy's
relationship to its original bean instance is a separate concern from
AOP proxy nesting.
The @MockitoSpyBean-specific strategy paragraph in the
@MockitoBean/@MockitoSpyBean documentation has also been revised
similarly, and now points to the new diagram.
See gh-37121
This commit documents how the Bean Override support in the TestContext
framework interacts with Spring AOP proxies created for annotations
such as @Transactional, @Cacheable, and @Retryable.
The new "Bean Overrides and Spring AOP Proxies" section in the general
Bean Overriding documentation explains that overrides using the WRAP
strategy (such as @MockitoSpyBean) end up as the target of any AOP
proxy subsequently created for the original bean; whereas, overrides
using the REPLACE or REPLACE_OR_CREATE strategy (such as @TestBean,
@MockitoBean) bypass the container's bean post-processing entirely and
therefore carry no AOP advice at all.
The new "@MockitoSpyBean and Spring AOP Proxies" section documents the
resulting stubbing and verification semantics. Verification via
Mockito's verify() API works transparently regardless of whether it is
invoked on the proxy or on the spy. Stubbing via doReturn(...)/doThrow(...)
is safe for stateless advice such as @Retryable, but can silently
corrupt the spy's configured answers for stateful or memoizing advice
such as @Cacheable, since the invocation used to declare a stub is
intercepted by Mockito before it reaches the spy and returns an empty
value that such advice may then cache.
AopTestUtils.getUltimateTargetObject(...) is documented as the way to
stub directly against the spy in that case.
The same section also documents how to disable the AOP advice for a
test altogether while leaving it in place in production code – for
example, binding a @Retryable attribute to a property placeholder
overridden via @TestPropertySource, or replacing the CacheManager with
a NoOpCacheManager via @TestBean.
Brief cross-referencing notes have also been added to the @TestBean
documentation and to the existing AopTestUtils description in the
"General Testing Utilities" section, to avoid duplicating the
explanation across pages.
In addition, the Javadoc for @MockitoSpyBean now contains a concise
WARNING summarizing these AOP proxy implications and linking to the new
reference documentation section for details.
Closes gh-37121
Property accessors resolved via SpEL's ReflectivePropertyAccessor and
DataBindingPropertyAccessor may be JavaBean-style accessors or plain
accessor methods used to support data classes such as Java records and
Kotlin data classes. However, neither accessor can determine, via
reflection, whether such a method is a side-effect-free read or an
action that happens to return a value. For example, File.delete() is a
public method that returns a boolean and therefore looks like a
plain "property".
To better inform users, this commit updates the Javadoc for
ReflectivePropertyAccessor, DataBindingPropertyAccessor, and
SimpleEvaluationContext (as well as in the SpEL reference
documentation) to clarify that restricting a SimpleEvaluationContext to
read-only data binding governs only whether assignment to a property is
permitted and does not guarantee that reading a property is free of
side effects. The reference documentation's "Security Considerations"
section now also defines what makes a method "accessor-shaped", with
concrete examples of safe versus side-effecting methods that share that
shape (for example, File.delete(), Queue.poll(), and
AtomicInteger.incrementAndGet()).
Building on that clarification, this commit introduces a new "Object
Design" section to the SpEL reference documentation, analogous to the
"Model Design" guidance for web data binding. This new section
recommends that any object reachable from an untrusted SpEL expression
(not only the root object) be a purpose-built, immutable type with a
deliberately limited surface area, and that its accessor-shaped methods
be audited for unsafe side effects. The new section also notes that
reachability is transitive through both property navigation and
indexing (for example, rootObject.child.grandchild or
rootObject.items[0]).
In any case, it remains the responsibility of the code that exposes a
root object or other reachable object to an expression from an
untrusted source to ensure that none of its accessor-shaped methods
perform an unsafe action.
Closes gh-37102
The Javadoc for EvaluationContext, StandardEvaluationContext, and
SimpleEvaluationContext previously repeated the same detailed
explanation of trusted sources and best-effort restrictions in four
places, making it hard to maintain and to digest.
This commit condenses each class-level warning to a succinct summary
that links to the new "Security Considerations" section of the SpEL
reference documentation, which remains the single, detailed source of
truth introduced in 9b42a40a2a.
See gh-36997
This commit adds a "Lifecycle and Reuse" section to the SpEL reference
documentation, immediately following the security considerations
introduced for gh-36997, explaining that AST nodes within a parsed
Expression may cache resolved PropertyAccessor, IndexAccessor,
MethodExecutor, and ConstructorExecutor instances for performance.
We also now document that reusing a parsed Expression across
EvaluationContext instances of the same type and with equivalent
configuration is supported (even if atypical), including when accessors
or resolvers registered with a context change between evaluations, but
that reusing a parsed Expression across contexts with different
security implications (for example, first against a
StandardEvaluationContext and later against a SimpleEvaluationContext)
is not supported, since cached state from a more permissive evaluation
may be reused during a more restrictive one.
The Javadoc for Expression, SpelExpression, EvaluationContext,
StandardEvaluationContext, SimpleEvaluationContext, PropertyAccessor,
IndexAccessor, MethodExecutor, and ConstructorExecutor has also been
updated to make these contracts discoverable via the API as well.
Closes gh-36968
This commit clarifies in the Javadoc for EvaluationContext,
StandardEvaluationContext, and SimpleEvaluationContext (as well as in
the SpEL reference documentation) that StandardEvaluationContext must
never be used to evaluate expressions from an untrusted source, and
that SimpleEvaluationContext's restricted language and feature subset
is only a best-effort measure. The updated documentation also defines a
"trusted" source as a developer or administrator of the application and
points out that it is the responsibility of the code that configures an
EvaluationContext to ensure that no object reachable via the context
exposes dangerous operations.
Closes gh-36997
Prior to this commit, the SpEL Indexer's PropertyAccessorValueRef could
reuse a cached PropertyAccessor for reads and writes even after that
accessor had been removed from the current EvaluationContext, leading
to stale property access if the EvaluationContext changes between
evaluations of the same expression.
This commit aligns PropertyAccessorValueRef with the analogous logic
in PropertyOrFieldReference and Indexer's IndexAccessorValueRef by
verifying that the cached PropertyAccessor is still registered in the
current EvaluationContext before reusing it in getValue() and
setValue().
Closes gh-36986
Prior to this commit, a SpEL ConstructorReference could reuse a cached
ConstructorExecutor even when the current EvaluationContext no longer
had any registered ConstructorResolvers, leading to inconsistent
behavior if the EvaluationContext changes between evaluations of the
same expression.
This commit aligns ConstructorReference with the analogous logic in
PropertyOrFieldReference by discarding the cached ConstructorExecutor
whenever there are no ConstructorResolvers registered in the current
EvaluationContext, ensuring that constructor resolution consistently
fails with a CONSTRUCTOR_NOT_FOUND exception in that scenario.
Closes gh-36985
Prior to this commit, SpEL's InlineList was cached as a mutable list in
compiled mode (i.e., in a static field in the generated byte code).
To address that, this commit modifies InlineList's generateClinitCode()
method so that it wraps both top-level and nested inline lists using
Collections.unmodifiableList(), analogous to what we already do in
createList().
Closes gh-37001
This commit picks up where ce718cf699 left off by ensuring that
findAllLocalMergedAnnotations() also finds annotations declared on
transitive interfaces — that is, on interfaces of the directly
implemented interfaces of the root declaring class.
The previous implementation filtered annotations from a single
TYPE_HIERARCHY search by checking whether the annotation's source was
either the root declaring class or one of its directly declared
interfaces. However, this excluded annotations inherited through an
interface chain such as First -> Second -> Third, where the annotation
is declared on Third but not on Second.
This commit replaces that approach with two targeted searches whose
results are combined:
- DIRECT on the root declaring class, to capture annotations declared
directly on the class (including via composed/meta-annotations)
- TYPE_HIERARCHY on each directly implemented interface, which
naturally traverses the full super-interface chain of each interface
A corresponding test for this scenario has also been added.
Closes gh-36975
Prior to this commit, AnnotationDescriptor's
findAllLocalMergedAnnotations() filtered results using
MergedAnnotationPredicates.firstRunOf(
MergedAnnotation::getAggregateIndex), which retains only annotations
that share the same aggregate index as the first annotation
encountered. Since annotations on the root declaring class have
aggregate index 0 and annotations on interfaces have higher indices,
interface annotations were silently excluded whenever the root
declaring class itself declared the annotation.
This commit fixes the issue by replacing the aggregate-index-based
filter with a source-based filter that explicitly includes annotations
from the root declaring class and from each directly implemented
interface. The Javadoc for findAllLocalMergedAnnotations() has also
been updated to document the ordering guarantee: annotations from the
root declaring class appear first, followed by annotations from
implemented interfaces in declaration order.
Closes gh-36975
This commit addresses warnings across the code base related to:
- internal and public deprecations in Spring Framework
- deprecated Locale constructors
- deprecated URL constructors
- deprecated Thread#getId method
After the upgrade to Gradle 9.6.0/9.6.1, the Gradle build started
emitting warnings due to use of deprecated APIs.
For example, Project.getProperties() is now annotated as @Deprecated
in Gradle 9.6 and will be removed in Gradle 10.0.
To avoid the warnings, this commit modifies:
- TestConventions to use `project.findProperty(...)` instead of
`project.getProperties().get(...)`
- framework-api.gradle to use `rootProject.ext.moduleProjects` instead
of simply `moduleProjects`
- framework-api.gradle and framework-bom.gradle to use
`project(<projectX>)` instead of simply `<projectX>`
- ide.gradle so that it no longer uses the deprecated `javaRuntimeName`
See gh-36952
Due to changes made in commit 41cd6879bd, MimeTypeUtils now raises a
StringIndexOutOfBoundsException instead of an InvalidMimeTypeException
when parsing certain invalid mime types -- for example, for a value
wrapped in double quotes which does not contain a ";" character.
To address that minor regression, this commit replaces
`mimeType.charAt(nextIndex - 1) != '\\'` with
`(nextIndex == 0 || mimeType.charAt(nextIndex - 1) != '\\')` to avoid
invoking `String#charAt` with a negative value.
See gh-36730
Closes gh-36971
Prior to this commit, DefaultServerRequest's ServletParametersMap lost
the original parameter order when entrySet() was invoked.
To address that, this commit revises ServletParametersMap.entrySet() so
that it stores the results in a LinkedHashSet, thereby retaining the
original order.
Closes gh-36966
In order to avoid the staircase scaling effect that results from our
current use of integer division, this commit revises applyJitter(long)
in ExponentialBackOffExecution to use floating-point (double) division
to calculate the applied jitter.
Closes gh-36943
This commit introduces support for tracking operations during SpEL
expression evaluation. If the maximum number of operations is exceeded,
a SpelEvaluationException is thrown.
The limit can be configured either on a per-use-case basis via
SpelParserConfiguration supplied to the SpelExpressionParser or
globally as a JVM system property or Spring property named
`spring.expression.maxOperations`.
Closes gh-36801
Prior to this commit, the `antora` Gradle task silently failed to build
the reference documentation, since Antora uses the latest LTS release
for Node.js by default, and the latest LTS apparently does not work for
us.