This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts-site.git
The following commit(s) were added to refs/heads/main by this push:
new 7e558d15d docs: audit Struts 7.3.0 and 6.11.0 release notes against
the documentation (#321)
7e558d15d is described below
commit 7e558d15d2dc6044e72dd0cf468c47c76b07b442
Author: Lukasz Lenart <[email protected]>
AuthorDate: Tue Aug 11 13:01:59 2026 +0200
docs: audit Struts 7.3.0 and 6.11.0 release notes against the documentation
(#321)
* docs: document Struts 7.3.0 multipart and upload-policy changes
WW-5474: struts.multipart.maxFiles now counts uploaded files only, and the
new struts.multipart.maxParameterCount (default 256) limits normal fields.
Both limits are fail-closed and enforced identically by the jakarta and
jakarta-stream parsers.
WW-5413: small uploads stay in memory; the temporary file is written lazily.
Document the new UploadedFile.getInputStream() / isMissing() default
methods.
WW-5659: lazy ${...} interceptor params resolve into a per-invocation
UploadPolicy; an unresolved expression now rejects the upload. Document the
acceptFile(UploadPolicy, ...) signature change and the WithLazyParams break.
Also correct the upload error-message keys: Struts 7.x uses the Commons
FileUpload 2 exception names, Struts 6.x the 1.x ones.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document struts.validators.skipValidatorsOnConversionError
WW-2934: an opt-in constant (default false) that skips a field's remaining
validators once that field has a type conversion error, so the user no
longer
sees both a conversion error and a redundant validator error. The conversion
field validator itself is never skipped.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document @TypeConversion key derivation and
ConversionRule.COLLECTION deprecation
WW-3871: @TypeConversion accepts a bare property name in `key` for every
ConversionRule at class, method and field level — the rule prefix is
derived.
The annotation is now also valid on fields (@Target METHOD, FIELD).
WW-5656: ConversionRule.COLLECTION and the Collection_ prefix are deprecated
since 7.3.0 in favour of ConversionRule.ELEMENT / Element_.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document localized-text cache tuning and request-locale validation
WW-5668: the AbstractLocalizedTextProvider caches are now size-bounded and
configurable via struts.i18n.cacheType / struts.i18n.cacheMaxSize, and
struts.locale.validateRequestLocale (opt-in) restricts request-derived
locales to the JVM's available-locale set on the Dispatcher path.
Both constants ship in Struts 6.11.0 and 7.3.0.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document struts.csp.report.maxSize
WW-5666: CspReportAction reads the submitted violation report up to a
bounded
length, defaulting to 8192 characters and configurable via
struts.csp.report.maxSize (accepted range 1..1048576). Oversized reports are
discarded and never reach processReport().
Ships in Struts 6.11.0 and 7.3.0.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document JSON plugin changes in Struts 7.3.0
WW-5652: Java records are serialized by iterating record components, @JSON
and
@JSONFieldBridge target record components, and Optional serializes as its
contained value or null.
WW-5641 / WW-5650: struts.json.writer / struts.json.reader overrides work
again (they were ignored in 7.2.x), and the interceptor obtains a fresh
reader/writer per request — custom implementations must be prototype-scoped.
WW-5666: struts.json.maxLength is enforced while reading, in fixed-size
chunks, and line terminators are no longer stripped (also in 6.11.0).
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document @StrutsParameter enforcement on creator-bound REST body
properties
WW-5642: constructor-bound properties (Java records, @JsonCreator,
@ConstructorProperties) bypassed the REST plugin's authorization wrapper up
to
7.2.1, so with struts.parameters.requireAnnotations enabled they were
populated
unchecked. Struts 7.3.0 authorizes them by path and redacts unauthorized
values, which means actions relying on unannotated record binding must now
authorize those properties explicitly.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document specificity ordering of annotated wildcard actions
WW-3784: @Action wildcard patterns were registered in arbitrary class-scan
order, so a broad pattern could non-deterministically shadow a narrower one
under first-match-wins. Struts 7.3.0 sorts annotated action names
most-specific-first; XML mappings keep their declared order.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document Weld client-proxy recognition in the CDI plugin
WW-5604: Struts resolved the target class behind Spring and Hibernate
proxies
but not behind Weld client proxies, so members of normal-scoped CDI beans
were
evaluated against the proxy class in SecurityMemberAccess. The plugin now
registers CdiProxyService as struts.proxyService out of the box; Weld
detection activates only when Weld is on the classpath.
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: simplify the creator-bound properties heading and its anchor
Co-Authored-By: Claude Opus 5 <[email protected]>
* docs: document the Log4j2 standardization completed in Struts 7.3.0
WW-5620 replaced the remaining java.util.logging and SLF4J call sites
inside the framework and dropped slf4j-api from struts2-core. Verified
against the STRUTS_7_3_0 tag: the dependency is absent from core/pom.xml
(it was declared optional in 7.2.1, so consumers see no classpath change)
and STRUTS_6_11_0 still declares it.
The same change removed the container's Logger binding, so an
@Inject java.util.logging.Logger in a container-managed bean no longer
resolves -- flagged as a behaviour change.
Co-Authored-By: Claude Opus 5 <[email protected]>
---------
Co-authored-by: Claude Opus 5 <[email protected]>
---
.../action-file-upload-interceptor.md | 44 +++++++++-
source/core-developers/action-file-upload.md | 94 ++++++++++++++++++----
source/core-developers/csp-interceptor.md | 11 +++
source/core-developers/file-upload.md | 12 ++-
source/core-developers/localization.md | 35 ++++++++
source/core-developers/logging.md | 12 +++
.../core-developers/struts-parameter-annotation.md | 19 +++++
.../core-developers/type-conversion-annotation.md | 30 ++++++-
source/core-developers/type-conversion.md | 5 +-
source/core-developers/validation.md | 21 +++++
source/core-developers/wildcard-mappings.md | 20 +++++
source/plugins/cdi/index.md | 21 +++++
source/plugins/json/index.md | 54 +++++++++++++
source/plugins/rest/index.md | 6 ++
14 files changed, 362 insertions(+), 22 deletions(-)
diff --git a/source/core-developers/action-file-upload-interceptor.md
b/source/core-developers/action-file-upload-interceptor.md
index 52e489e8b..bc4bc9257 100644
--- a/source/core-developers/action-file-upload-interceptor.md
+++ b/source/core-developers/action-file-upload-interceptor.md
@@ -33,6 +33,8 @@ You can override the text of these messages by providing text
for the following
types specified
- `struts.messages.error.file.extension.not.allowed` - occurs when the
uploaded file does not match the expected
file extensions specified
+ - `struts.messages.error.upload.policy.unresolved` - occurs when a `${...}`
parameter could not be resolved for the
+ current invocation, so the file is rejected (since Struts 7.3.0)
## Parameters
@@ -61,10 +63,48 @@ enabling per-request dynamic validation. This is available
when used with `WithL
The expressions are evaluated against the ValueStack at the time of the
upload, allowing your action to provide
dynamic values based on the current request context.
+Since Struts 7.3.0 the resolved values are held in a per-invocation
`org.apache.struts2.interceptor.UploadPolicy`
+instead of being written onto the shared interceptor instance, so two
concurrent requests can no longer be validated
+against each other's policy — see
[WW-5659](https://issues.apache.org/jira/browse/WW-5659).
+
+Also since Struts 7.3.0, an expression that cannot be resolved makes the
policy unusable and the upload is **rejected**
+with the `struts.messages.error.upload.policy.unresolved` message, rather than
silently validated against a
+partially-resolved policy. A typo in a `${...}` parameter, or a parameter name
with no matching property, now fails
+closed instead of relaxing validation. The `disabled` parameter is exempt:
unresolved it is simply `false`, which leaves
+the interceptor running with the rest of the policy intact.
+{:.alert .alert-warning}
+
## Extending the Interceptor
-You can extend this interceptor and override the acceptFile method to provide
more control over which files are supported
-and which are not.
+You can extend this interceptor and override the `acceptFile` method to
provide more control over which files are
+supported and which are not.
+
+Since Struts 7.3.0 `acceptFile` takes the per-invocation policy as its first
argument:
+
+```java
+protected boolean acceptFile(UploadPolicy policy, Object action, UploadedFile
file,
+ String originalFilename, String contentType,
String inputName)
+```
+
+Read `policy.getMaximumSize()`, `policy.getAllowedTypes()` and
`policy.getAllowedExtensions()` instead of the former
+interceptor fields, which are configuration-time state only. Subclasses
overriding the old five-argument signature must
+be updated — the old method is gone, so the compiler will point them out.
+{:.alert .alert-warning}
+
+The same release changed the `org.apache.struts2.interceptor.WithLazyParams`
interface, which is now generic over an
+`org.apache.struts2.interceptor.InterceptorParams` holder:
+
+```java
+public interface WithLazyParams<P extends InterceptorParams> {
+ P newLazyParams();
+ String intercept(ActionInvocation invocation, P lazyParams) throws
Exception;
+}
+```
+
+Third-party interceptors implementing `WithLazyParams` must supply a holder
class (extend
+`org.apache.struts2.interceptor.DisableParams` if the interceptor supports the
`disabled` parameter) and move the
+resolved values off the singleton into it.
+{:.alert .alert-warning}
## Examples
diff --git a/source/core-developers/action-file-upload.md
b/source/core-developers/action-file-upload.md
index 289b5090e..f19f958aa 100644
--- a/source/core-developers/action-file-upload.md
+++ b/source/core-developers/action-file-upload.md
@@ -157,6 +157,7 @@ struts.multipart.parser=jakarta
struts.multipart.saveDir= # Filesystem location to save parsed request data
struts.multipart.maxSize=2097152 # Max combined size of files per request
struts.multipart.maxFiles=256 # Max number of files per request
+struts.multipart.maxParameterCount=256 # Max number of normal fields per
request (since Struts 7.3.0)
struts.multipart.maxFileSize= # Max size per file per request
struts.multipart.maxStringLength=4096 # Max length of a string parameter (a
normal field) in a multipart request (since Struts 6.1.2.1)
```
@@ -166,9 +167,8 @@ further details on these options first.
### Files Number Limit
-Since Struts 6.1.2 a new option was added, which uses Commons FileUpload
feature to limit how many files can be
-uploaded at once, in one request. This option requires to use Commons
FileUpload ver. 1.5 at least and by default is set
-to **256**. Please always set this to a finite value to prevent DoS attacks.
+Since Struts 6.1.2 a new option was added to limit how many files can be
uploaded at once, in one request. By default it
+is set to **256**. Please always set this to a finite value to prevent DoS
attacks.
To change this value define a constant in `struts.xml` as follows:
@@ -178,8 +178,31 @@ To change this value define a constant in `struts.xml` as
follows:
</struts>
```
-**Note**: This limit also affects number of normal fields in the request,
there is an open bug in the Commons FileUpload
-library to address this problem, see
[FILEUPLOAD-351](https://issues.apache.org/jira/browse/FILEUPLOAD-351).
+### Parameters Number Limit
+
+> Since Struts 7.3.0
+
+`struts.multipart.maxFiles` now counts **uploaded files only**, and a
companion option
+`struts.multipart.maxParameterCount` (default **256**) limits the number of
normal, non-file fields in a multipart
+request. The two limits are independent: a request may carry up to `maxFiles`
files **and** up to `maxParameterCount`
+form fields. Both are enforced identically by the `jakarta` and the
`jakarta-stream` parser.
+
+```xml
+<struts>
+ <constant name="struts.multipart.maxFiles" value="500"/>
+ <constant name="struts.multipart.maxParameterCount" value="1000"/>
+</struts>
+```
+
+Exceeding either limit is fail-closed: parsing is aborted, an upload error is
recorded, and the action receives **no**
+parameters and **no** files — never a partially populated request.
+
+**Behaviour change in Struts 7.3.0**: before 7.3.0,
`struts.multipart.maxFiles` capped files *and* normal fields
+together (`jakarta` parser) or counted distinct file field names
(`jakarta-stream` parser), so a form with many normal
+fields and few files could be rejected — see
[WW-5474](https://issues.apache.org/jira/browse/WW-5474). Such requests now
+pass, while a request with more than `maxParameterCount` normal fields is
rejected with the new
+`struts.messages.upload.error.FileUploadParameterCountLimitException` message.
If you raised `maxFiles` only to
+accommodate large forms, lower it back to a realistic file count and raise
`struts.multipart.maxParameterCount` instead.
{:.alert .alert-warning}
### File Size Limits
@@ -274,16 +297,23 @@ or extends `com.opensymphony.xwork2.ActionSupport`. These
error messages are bas
struts-messages.properties, a default i18n file processed for all i18n
requests. You can override the text of these
messages by providing text for the following keys:
-| Error Key | Description
|
-|----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
-| `struts.messages.error.uploading` | A general
error that occurs when the file could not be uploaded
|
-| `struts.messages.error.file.too.large` | Occurs when
the uploaded file is too large as specified by maximumSize.
|
-| `struts.messages.error.content.type.not.allowed` | Occurs when
the uploaded file does not match the expected content types specified
|
-| `struts.messages.error.file.extension.not.allowed` | Occurs when
uploaded file has disallowed extension
|
-| `struts.messages.upload.error.SizeLimitExceededException` | Occurs when
the upload request (as a whole) exceed configured **struts.multipart.maxSize**
|
-| `struts.messages.upload.error.FileSizeLimitExceededException` | Occurs when
a file within the upload request exceeds configured
**struts.multipart.maxFileSize** |
-| `struts.messages.upload.error.FileCountLimitExceededException` | Occurs when
the number of files in the upload request exceeds configured
**struts.multipart.maxFiles** |
-| `struts.messages.upload.error.<Exception class SimpleName>` | Occurs when
any other exception took place during file upload process
|
+| Error Key |
Description
|
+|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
+| `struts.messages.error.uploading` | A
general error that occurs when the file could not be uploaded
|
+| `struts.messages.error.file.too.large` |
Occurs when the uploaded file is too large as specified by maximumSize.
|
+| `struts.messages.error.content.type.not.allowed` |
Occurs when the uploaded file does not match the expected content types
specified |
+| `struts.messages.error.file.extension.not.allowed` |
Occurs when uploaded file has disallowed extension
|
+| `struts.messages.error.upload.policy.unresolved` |
Occurs when a `${...}` interceptor parameter could not be resolved, see
[Dynamic Parameter
Evaluation](action-file-upload-interceptor#dynamic-parameter-evaluation) (since
Struts 7.3.0) |
+| `struts.messages.upload.error.FileUploadSizeException` |
Occurs when the upload request (as a whole) exceeds configured
**struts.multipart.maxSize** |
+| `struts.messages.upload.error.FileUploadByteCountLimitException` |
Occurs when a file within the upload request exceeds configured
**struts.multipart.maxFileSize** |
+| `struts.messages.upload.error.FileUploadFileCountLimitException` |
Occurs when the number of files in the upload request exceeds configured
**struts.multipart.maxFiles** |
+| `struts.messages.upload.error.FileUploadParameterCountLimitException` |
Occurs when the number of normal fields in the upload request exceeds
configured **struts.multipart.maxParameterCount** (since Struts 7.3.0) |
+| `struts.messages.upload.error.<Exception class SimpleName>` |
Occurs when any other exception took place during file upload process
|
+
+The exception-based keys above are the Commons FileUpload 2 names used since
Struts 7.0.0. Struts 6.x uses the Commons
+FileUpload 1.x names instead: `SizeLimitExceededException`,
`FileSizeLimitExceededException`
+and `FileCountLimitExceededException`.
+{:.alert .alert-info}
### Temporary Directories
@@ -294,6 +324,40 @@ to the directory where the uploaded files will be placed.
If this property is no
to `javax.servlet.context.tempdir`. Keep in mind that on some operating
systems, like Solaris, `/tmp` is memory based
and files stored in that directory would consume an amount of RAM
approximately equal to the size of the uploaded file.
+### In-Memory Uploads
+
+> Since Struts 7.3.0
+
+Small uploads (below the parser's disk-spill threshold, around 8 KB) are kept
in memory and are **no longer written to a
+temporary file eagerly** — see
[WW-5413](https://issues.apache.org/jira/browse/WW-5413). The temporary file is
written
+lazily, only when something asks for a `java.io.File`. Uploads rejected by
size, content-type or extension checks
+therefore never touch the filesystem at all.
+
+Two methods were added to
`org.apache.struts2.dispatcher.multipart.UploadedFile` to support this. Both
are `default`
+methods, so existing third-party implementations keep compiling:
+
+| Method | Purpose
|
+|-----------------------------------|----------------------------------------------------------------------------------------------------|
+| `InputStream getInputStream()` | Reads the uploaded content without
forcing it to disk — the preferred way to consume an upload |
+| `boolean isMissing()` | Reports a failed upload with no content,
answered without materialising the content |
+
+```java
+public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
+ for (UploadedFile file : uploadedFiles) {
+ try (InputStream in = file.getInputStream()) {
+ // process the bytes; no temporary file is created for small
uploads
+ } catch (IOException e) {
+ // handle
+ }
+ }
+}
+```
+
+`getContent()` and `getAbsolutePath()` still return a `java.io.File` as
before, so existing code — including actions
+using the legacy `File`-typed property — keeps working unchanged; the first
such call simply materialises the temporary
+file at that point. Prefer `getInputStream()` in new code when you only need
the bytes.
+{:.alert .alert-info}
+
### Alternate Libraries
The `struts.multipart.parser` used by the fileUpload interceptor to handle
HTTP POST requests, encoded using the
diff --git a/source/core-developers/csp-interceptor.md
b/source/core-developers/csp-interceptor.md
index 6bc69bf51..d0d35f7d8 100644
--- a/source/core-developers/csp-interceptor.md
+++ b/source/core-developers/csp-interceptor.md
@@ -46,6 +46,17 @@ Read JavaDoc of the action for more details.
> Note: the action must always return an HTTP status `204`.
+Since Struts 6.11.0 and 7.3.0 the submitted report body is read up to a
bounded length instead of being read whole.
+The limit defaults to **8192** characters and is configurable:
+
+```xml
+<constant name="struts.csp.report.maxSize" value="16384"/>
+```
+
+A report larger than the limit is discarded with a warning and
`processReport(String)` is not called. Accepted values
+are `1` to `1048576`; anything outside that range is ignored, and the default
applies. Raise the limit if your
+browsers submit larger violation reports than the default allows.
+
## Action aware
Since Struts 6.2.0 it is possible to configure the CSP interceptor by
providing the an instance of `CspSettings` interface.
diff --git a/source/core-developers/file-upload.md
b/source/core-developers/file-upload.md
index d7811940a..46500d5c2 100644
--- a/source/core-developers/file-upload.md
+++ b/source/core-developers/file-upload.md
@@ -261,6 +261,7 @@ struts.multipart.parser=jakarta
struts.multipart.saveDir= # Filesystem location to save parsed request data
struts.multipart.maxSize=2097152 # Max combined size of files per request
struts.multipart.maxFiles=256 # Max number of files per request
+struts.multipart.maxParameterCount=256 # Max number of normal fields per
request (since Struts 7.3.0)
struts.multipart.maxFileSize= # Max size per file per request
struts.multipart.maxStringLength=4096 # Max length of a string parameter (a
normal field) in a multipart request (since Struts 6.1.2.1)
```
@@ -282,8 +283,10 @@ To change this value define a constant in `struts.xml` as
follows:
</struts>
```
-**Note**: This limit also affects number of normal fields in the request,
there is an open bug in the Commons FileUpload
-library to address this problem, see
[FILEUPLOAD-351](https://issues.apache.org/jira/browse/FILEUPLOAD-351).
+**Note**: Up to Struts 7.2.x this limit also affects the number of normal
fields in the request, see
+[FILEUPLOAD-351](https://issues.apache.org/jira/browse/FILEUPLOAD-351). Since
Struts 7.3.0 `struts.multipart.maxFiles`
+counts uploaded files only, and normal fields are limited separately by
`struts.multipart.maxParameterCount`
+(default **256**) — see [Parameters Number
Limit](action-file-upload#parameters-number-limit).
{:.alert .alert-warning}
### File Size Limits
@@ -388,6 +391,11 @@ messages by providing text for the following keys:
| `struts.messages.upload.error.FileCountLimitExceededException` | Occurs when
the number of files in the upload request exceeds configured
**struts.multipart.maxFiles** |
| `struts.messages.upload.error.<Exception class SimpleName>` | Occurs when
any other exception took place during file upload process
|
+The exception-based keys above are the Commons FileUpload 1.x names used by
Struts 6.x. Since Struts 7.0.0 the
+Commons FileUpload 2 names apply instead — see
+[Error Messages](action-file-upload#error-messages) on the Action File Upload
page.
+{:.alert .alert-info}
+
### Temporary Directories
All uploaded files are saved to a temporary directory by the framework before
being passed in to an Action. Depending on
diff --git a/source/core-developers/localization.md
b/source/core-developers/localization.md
index 0050fae32..3352ef552 100644
--- a/source/core-developers/localization.md
+++ b/source/core-developers/localization.md
@@ -155,6 +155,41 @@ will search the default bundles first. In some cases this
can improve overall ap
> More details can be found in
> [WW-5112](https://issues.apache.org/jira/browse/WW-5112) and the linked PR.
+## Tuning the localized-text caches
+
+> Since Struts 6.11.0 and 7.3.0
+
+`AbstractLocalizedTextProvider` caches resolved bundles, missing-bundle
misses, message formats and the class/package
+hierarchy lookups. Those caches are size-bounded and use the same cache
abstraction as the
+[OGNL caches](ognl-cache-configuration), configured with two constants:
+
+```xml
+<constant name="struts.i18n.cacheType" value="wtlfu"/>
+<constant name="struts.i18n.cacheMaxSize" value="10000"/>
+```
+
+`struts.i18n.cacheType` accepts `basic` (unbounded `ConcurrentHashMap`), `lru`
or `wtlfu` (Window TinyLFU, the default).
+`struts.i18n.cacheMaxSize` (default `10000`) applies to each cache
individually. Every entry is fully reconstructible
+on a miss, so eviction only costs a recompute — it can never yield a stale or
wrong message. A very large application
+that resolves more than 10 000 distinct message keys may want to raise
`struts.i18n.cacheMaxSize` to avoid eviction
+churn.
+
+## Validating the request locale
+
+> Since Struts 6.11.0 and 7.3.0
+
+When `struts.locale` is not set, the framework derives the locale from the
request (the `Accept-Language` header).
+`Dispatcher` used to take that value as-is, while [I18n
Interceptor](i18n-interceptor) already checked its own resolved
+locale against the JVM's available locales. Set the following constant to
apply the same check on the `Dispatcher`
+path:
+
+```xml
+<constant name="struts.locale.validateRequestLocale" value="true"/>
+```
+
+The default is `false`, preserving the previous behaviour. With `true`, a
request-derived locale that is not in the
+JVM's available-locale set falls back to the default locale instead of being
used.
+
## Using only global bundles
If you don't need to use the package-scan-functionality and only base on the
global bundles (those provided by
diff --git a/source/core-developers/logging.md
b/source/core-developers/logging.md
index 220f41524..944033349 100644
--- a/source/core-developers/logging.md
+++ b/source/core-developers/logging.md
@@ -24,6 +24,18 @@ setting `-Dxwork.loggerFactory` has no effect.
The Log4j2 bridges for third-party libraries (`log4j-jcl` for Commons Logging,
`log4j-slf4j-impl` for SLF4J) are a
separate concern and remain available. They route logging emitted by
transitive dependencies into Log4j2.
+Struts 7.3.0 finished the job: the last `java.util.logging` and SLF4J call
sites inside the framework — in the
+dependency-injection container, its finalizable reference queue and the Tiles
plugin's Velocity renderable — now log
+through Log4j2, and the `slf4j-api` dependency was dropped from
`struts2-core`, see
+[WW-5620](https://issues.apache.org/jira/browse/WW-5620). That dependency was
declared `optional`, so it was never
+propagated to applications and nothing changes on your classpath. Struts
6.11.0 still declares it.
+
+**Removed in Struts 7.3.0**: the internal DI container no longer provides a
`Logger` binding. `@Inject`-ing a
+`java.util.logging.Logger` into a container-managed bean used to yield a JUL
logger named after the declaring class;
+that binding was removed along with the JUL usage, so such an injection no
longer resolves. Declare a Log4j2 logger
+directly instead, as shown below.
+{:.alert .alert-warning}
+
## Usage
Declare a Log4j2 logger in your own classes as usual:
diff --git a/source/core-developers/struts-parameter-annotation.md
b/source/core-developers/struts-parameter-annotation.md
index 97e3c37ff..916607c7b 100644
--- a/source/core-developers/struts-parameter-annotation.md
+++ b/source/core-developers/struts-parameter-annotation.md
@@ -29,6 +29,25 @@ channel that can populate an action from request data:
- [JSON](../../plugins/json) and [REST](../../plugins/rest) plugins —
per-property
authorization performed during deserialization, so unauthorized fields are
never set.
+### Creator-bound properties
+
+Up to Struts 7.2.1 the REST plugin's authorization wrapper covered only
properties Jackson populates through a setter,
+a field or a builder. Properties bound through a **constructor** — Java
records, `@JsonCreator` constructors and
+`@ConstructorProperties` — were deserialized on a path that bypassed the
wrapper entirely, so with
+`struts.parameters.requireAnnotations` enabled a record-typed value anywhere
in a REST request body was populated with
+no authorization check at all. This is fixed in Struts 7.3.0, see
+[WW-5642](https://issues.apache.org/jira/browse/WW-5642).
+
+Creator-bound properties are now authorized like any other property, by their
path. Values that are not authorized are
+redacted — the component is left unset rather than taking the client-supplied
value — and if a record's own constructor
+rejects that (a compact constructor calling `Objects.requireNonNull`, or
`FAIL_ON_NULL_FOR_PRIMITIVES`), the whole
+object under construction is dropped instead of failing the request.
+
+If a REST action relied on record-typed request-body properties binding
without annotations, they now need authorizing
+the same way as any nested object: `@StrutsParameter(depth = ...)` on the
getter that reaches them, or a `ModelDriven`
+model. Otherwise those values silently stop arriving.
+{:.alert .alert-warning}
+
## ModelDriven actions
When an action implements `ModelDriven` and the [Model Driven
diff --git a/source/core-developers/type-conversion-annotation.md
b/source/core-developers/type-conversion-annotation.md
index 26aacdd1e..56fad7e72 100644
--- a/source/core-developers/type-conversion-annotation.md
+++ b/source/core-developers/type-conversion-annotation.md
@@ -23,7 +23,28 @@ This annotation is used for class and application wide
conversion rules.
## Usage
-The `TypeConversion` annotation can be applied at property and method level.
+The `TypeConversion` annotation can be applied at method level, and since
Struts 7.3.0 also at field level.
+
+## Key derivation
+
+> Since Struts 7.3.0
+
+The `key` parameter accepts a bare property name for every `ConversionRule`;
the rule's prefix is derived
+automatically. It used to be derived only for method-level annotations, so
class-level `@Conversion(conversions = ...)`
+entries had to spell the prefix out:
+
+```java
+// before Struts 7.3.0 — prefix spelled out
+@Conversion(conversions = @TypeConversion(key = "CreateIfNull_users", rule =
ConversionRule.CREATE_IF_NULL, value = "true"))
+
+// since Struts 7.3.0 — the CreateIfNull_ prefix is derived
+@Conversion(conversions = @TypeConversion(key = "users", rule =
ConversionRule.CREATE_IF_NULL, value = "true"))
+```
+
+Existing annotations that already carry a prefix keep working: a `key`
starting with any known rule prefix is used
+as-is, never prefixed twice. The prefixes are `CreateIfNull_`, `Element_`,
`Key_` and `KeyProperty_`;
+`ConversionRule.PROPERTY` and `ConversionRule.MAP` have no prefix of their
own. Keys of
+`ConversionType.APPLICATION` annotations are class names and are never
prefixed.
## Parameters
@@ -78,6 +99,11 @@ The `TypeConversion` annotation can be applied at property
and method level.
</p>
+`ConversionRule.COLLECTION` is deprecated since Struts 7.3.0 — use
`ConversionRule.ELEMENT` instead. The two are handled
+identically by the engine, and `ELEMENT` additionally covers the values of a
`Map`. Existing annotations using
+`COLLECTION` keep working and produce the deprecated `Collection_xxx` key,
which is still read as a fallback.
+{:.alert .alert-warning}
+
## Examples
```java
@@ -103,7 +129,7 @@ The `TypeConversion` annotation can be applied at property
and method level.
this.convertDouble = convertDouble;
}
- @TypeConversion(rule = ConversionRule.COLLECTION, converterClass =
String.class)
+ @TypeConversion(rule = ConversionRule.ELEMENT, converterClass =
String.class)
@StrutsParameter
public void setUsers( List users ) {
this.users = users;
diff --git a/source/core-developers/type-conversion.md
b/source/core-developers/type-conversion.md
index f52d61ef6..d7464cf5e 100644
--- a/source/core-developers/type-conversion.md
+++ b/source/core-developers/type-conversion.md
@@ -226,7 +226,10 @@ contained within Maps and Collections. For Collections,
such as Lists, the eleme
and the value may be specified by using the pattern `Key_xxx` and
`Element_xxx`, respectively.
From WebWork 2.1.x, the `Collection_xxx` format is still supported and
honored, although it is deprecated and will be
-removed eventually.
+removed eventually. Since Struts 7.3.0 the matching
`ConversionRule.COLLECTION` enum constant used by the
+[TypeConversion Annotation](type-conversion-annotation) is marked
`@Deprecated` as well — use `ConversionRule.ELEMENT`,
+which produces the current `Element_xxx` key and additionally covers the
values of a `Map`.
+{:.alert .alert-warning}
Additionally, you can create your own custom `ObjectTypeDeterminer` by
implementing the `ObjectTypeDeterminer` interface.
There is also an optional `ObjectTypeDeterminer` that utilizes Java 5
generics. See the [Annotations](annotations)
diff --git a/source/core-developers/validation.md
b/source/core-developers/validation.md
index 4ce33c2bc..ff2eb799a 100644
--- a/source/core-developers/validation.md
+++ b/source/core-developers/validation.md
@@ -516,6 +516,27 @@ both validators will be run, even if the "required"
validator short-circuits. "r
and will not short-circuit the plain ExpressionValidator because
FieldValidators only short-circuit other checks on that
same field. Since the plain Validator is not field specific, it is not
short-circuited.
+## Skipping Validators on a Conversion Error
+
+> Since Struts 7.3.0
+
+When a field fails [type conversion](type-conversion) — a user typing `one`
into an `Integer age` field, for example —
+the [Conversion Error Interceptor](conversion-error-interceptor) records a
conversion error before the validators run.
+The field's own validators then run against a value the user never entered, so
a `requiredstring` or `int` validator
+typically adds a second, redundant error for the same field.
+
+Set the following constant to skip a field's remaining validators once that
field has a conversion error:
+
+```xml
+<struts>
+ <constant name="struts.validators.skipValidatorsOnConversionError"
value="true"/>
+</struts>
+```
+
+The default is `false`, which preserves the long-standing behaviour of
reporting both errors. The `conversion` field
+validator itself is never skipped, so a custom conversion message and the
`repopulateField` behaviour of
+[Conversion Validator](conversion-validator) keep working. Action-level
(non-field) validators are unaffected.
+
## How Validators of an Action are Found
As mentioned above, the framework will also search up the inheritance tree of
the action to find default validations
diff --git a/source/core-developers/wildcard-mappings.md
b/source/core-developers/wildcard-mappings.md
index 0082c0440..5a83792cf 100644
--- a/source/core-developers/wildcard-mappings.md
+++ b/source/core-developers/wildcard-mappings.md
@@ -83,6 +83,26 @@ Also, the action mapping and action result properties will
accept wildcard-match
> See also [Wildcard Method](../getting-started/wildcard-method-selection)
+### Ordering of annotated wildcard actions
+
+> Since Struts 7.3.0
+
+The ordering rule above relies on the *physical order* of the mappings, which
XML gives you but annotations do not:
+actions declared with the [Convention Plugin](../plugins/convention/)'s
`@Action` were registered in class-scan order,
+which is effectively arbitrary and can differ between JVMs and classloaders. A
broad pattern registered ahead of a
+narrower one it also matches could therefore shadow it, non-deterministically
— see
+[WW-3784](https://issues.apache.org/jira/browse/WW-3784).
+
+Annotated wildcard action names are now sorted most-specific-first before
registration, using these keys in order:
+
+1. fewer wildcard tokens first (a `*` / `**` run, or a `{var}` group);
+2. more literal characters first;
+3. fewer path-spanning `**` tokens first;
+4. alphabetical order of the pattern, as a deterministic tie-breaker.
+
+The comparison recognises both `*` / `**` (the default `WildcardHelper`
matcher) and `{var}`
+(`NamedVariablePatternMatcher`). XML mappings are unaffected — they keep their
declared order.
+
## Parameters in namespaces
From Struts 2.1+ namespace patterns can be extracted as request parameters and
bound to the action. To enable this
diff --git a/source/plugins/cdi/index.md b/source/plugins/cdi/index.md
index bdcab636f..1c7887e20 100644
--- a/source/plugins/cdi/index.md
+++ b/source/plugins/cdi/index.md
@@ -143,6 +143,27 @@ the right name. Just add a constant like this to your
struts.xml to be on the ha
```
+### Proxied beans
+
+> Since Struts 7.3.0
+
+Normal-scoped CDI beans (`@SessionScoped`, `@RequestScoped`,
`@ApplicationScoped`, …) are injected as client proxies.
+Struts resolves the real target class of a proxy before evaluating the OGNL
member allowlist, but only knew about
+Spring and Hibernate proxies — a Weld client proxy was not recognised, so
members reached through it were evaluated
+against the proxy class, see
[WW-5604](https://issues.apache.org/jira/browse/WW-5604).
+
+The plugin now registers its own `ProxyService` implementation, which adds
Weld client-proxy detection on top of the
+default one:
+
+```xml
+<bean type="org.apache.struts2.util.ProxyService" name="cdi"
class="org.apache.struts2.cdi.CdiProxyService"/>
+<constant name="struts.proxyService" value="cdi"/>
+```
+
+Both lines ship in the plugin's `struts-plugin.xml`, so nothing has to be
configured. Weld detection activates only
+when Weld is on the classpath; with any other CDI implementation the plugin
behaves exactly as the default
+`ProxyService` did. Override `struts.proxyService` only if you supply your own
implementation.
+
## Usage
CDI has an extremely rich feature set, and this section is not intended as a
replacement for the CDI reference
diff --git a/source/plugins/json/index.md b/source/plugins/json/index.md
index 9be23a5ad..f8d159ddd 100644
--- a/source/plugins/json/index.md
+++ b/source/plugins/json/index.md
@@ -279,6 +279,42 @@ Enable this parameter through `struts.xml`:
</result>
```
+### Java records and Optional
+
+> Since Struts 7.3.0
+
+Java records are serialized by iterating their record components, so a record
no longer has to be wrapped in a bean to
+be returned as JSON:
+
+```java
+public record Person(String name, int age) {}
+```
+
+```json
+{"name": "Alice", "age": 30}
+```
+
+The `@JSON` and `@JSONFieldBridge` annotations now also target record
components, so they can be declared on the
+component itself and are honoured on the generated accessor:
+
+```java
+public record Person(String name, @JSON(name = "years") int age) {}
+```
+
+The `excludeProperties` / `includeProperties` result parameters apply to
record components as they do to bean
+properties.
+
+`java.util.Optional` values are serialized as the contained value, or as
`null` when empty — an empty `Optional` is
+therefore subject to `excludeNullProperties` like any other null:
+
+```java
+public record Person(String name, Optional<String> nickname) {}
+```
+
+```json
+{"name": "Alice", "nickname": null}
+```
+
### Compressing the output
Set the `enableGZIP` attribute to true to gzip the generated json response.
The request **must** include `gzip`
@@ -383,6 +419,18 @@ The implementation should then be defined in `struts.xml`
like:
</struts>
```
+The `struts.json.writer` and `struts.json.reader` overrides were ignored in
Struts 7.2.x — the plugin's bean selection
+ran before the application's `struts.xml` was folded in, freezing the binding
to the built-in implementations. This is
+fixed in Struts 7.3.0, see
[WW-5641](https://issues.apache.org/jira/browse/WW-5641); if you carried a
workaround for
+that regression, you can drop it.
+{:.alert .alert-warning}
+
+Declare the bean with `scope="prototype"`, as in the example above. Since
Struts 7.3.0 the `json` interceptor obtains a
+fresh reader and writer per request, and the built-in implementations keep
their parse/write state in plain instance
+fields — cross-request safety relies on the bean being prototype-scoped. A
singleton-scoped custom implementation that
+holds per-request state will leak it between concurrent requests.
+{:.alert .alert-warning}
+
There is an example at
[struts-examples/json-customize/FlexJSONWriter.java](https://gitbox.apache.org/repos/asf?p=struts-examples.git;a=blob_plain;f=json-customize/src/main/java/org/demo/FlexJSONWriter.java;hb=HEAD).
It replaces Struts default json serializer with
[Flexjson](http://flexjson.sourceforge.net/) as below:
@@ -580,6 +628,12 @@ a `<param>` (following the same pattern as the fileUpload
interceptor):
<constant name="struts.json.maxLength" value="1048576"/>
```
+Since Struts 6.11.0 and 7.3.0 `struts.json.maxLength` is evaluated **while**
the input is read, in fixed-size chunks,
+rather than after accumulating each line, so enforcement no longer depends on
how the payload is split into lines. As
+part of the same change, line terminators are no longer stripped while
reading: they are insignificant whitespace
+between tokens, but an unescaped control character inside a string value is
now preserved rather than silently removed.
+{:.alert .alert-info}
+
The reader and writer implementations are also pluggable via
`struts.json.reader` and `struts.json.writer` (both default to `struts`).
diff --git a/source/plugins/rest/index.md b/source/plugins/rest/index.md
index ba7790930..380b51bc0 100644
--- a/source/plugins/rest/index.md
+++ b/source/plugins/rest/index.md
@@ -327,6 +327,12 @@ same property-level authorization should implement
`AuthorizationAwareContentTypeHandler` (which extends `ContentTypeHandler` with
authorization callbacks) instead of `ContentTypeHandler` directly.
+Since Struts 7.3.0 that enforcement also covers **constructor-bound**
properties — Java records, `@JsonCreator`
+constructors and `@ConstructorProperties` — which previously bypassed it. If
your controllers accept records in the
+request body, make sure they are reachable from an authorized property; see
+[Creator-bound
properties](../../core-developers/struts-parameter-annotation.html#creator-bound-properties).
+{:.alert .alert-warning}
+
### Settings
The following settings can be customized. See the [developer
guide](/core-developers/configuration-files).