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.git
The following commit(s) were added to refs/heads/main by this push:
new 11c10ee8f WW-5620 Standardize logging on Log4j2 (#1794)
11c10ee8f is described below
commit 11c10ee8f9259240285369c6feaae1fe6c06fc0d
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Jul 20 13:41:45 2026 +0200
WW-5620 Standardize logging on Log4j2 (#1794)
* WW-5620 docs: add Log4j2 logging standardization design spec
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* WW-5620 docs: add Log4j2 logging standardization implementation plan
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* WW-5620 Migrate FinalizableReferenceQueue to Log4j2
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* WW-5620 Migrate AbstractDefaultToStringRenderable to Log4j2
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* WW-5620 Remove unused injectable j.u.l.Logger DI factory from
ContainerBuilder
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* WW-5620 Remove dead first-party SLF4J dependency declarations
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---------
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
core/pom.xml | 12 -
.../apache/struts2/inject/ContainerBuilder.java | 20 --
.../inject/util/FinalizableReferenceQueue.java | 9 +-
...07-20-WW-5620-log4j2-logging-standardization.md | 345 +++++++++++++++++++++
...W-5620-log4j2-logging-standardization-design.md | 135 ++++++++
parent/pom.xml | 11 -
.../AbstractDefaultToStringRenderable.java | 6 +-
pom.xml | 1 -
8 files changed, 487 insertions(+), 52 deletions(-)
diff --git a/core/pom.xml b/core/pom.xml
index b5303545f..6509b6a42 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -316,18 +316,6 @@
<scope>test</scope>
</dependency>
- <!-- SLF4J support -->
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- <scope>test</scope>
- </dependency>
-
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
diff --git a/core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java
b/core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java
index 3d5f7f6b4..0d7bc6b4e 100644
--- a/core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java
+++ b/core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java
@@ -16,14 +16,12 @@
package org.apache.struts2.inject;
-import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.logging.Logger;
/**
* Builds a dependency injection {@link Container}. The combination of
@@ -34,7 +32,6 @@ import java.util.logging.Logger;
*
* <ul>
* <li>Injects the current {@link Container}.
- * <li>Injects the {@link Logger} for the injected member's declaring class.
* </ul>
*
* @author [email protected] (Bob Lee)
@@ -60,29 +57,12 @@ public final class ContainerBuilder {
}
};
- private static final InternalFactory<Logger> LOGGER_FACTORY =
- new InternalFactory<>() {
- public Logger create(InternalContext context) {
- Member member = context.getExternalContext().getMember();
- return member == null ? Logger.getAnonymousLogger()
- :
Logger.getLogger(member.getDeclaringClass().getName());
- }
-
- @Override
- public Class<? extends Logger> type() {
- return Logger.class;
- }
- };
-
/**
* Constructs a new builder.
*/
public ContainerBuilder() {
// In the current container as the default Container implementation.
factories.put(Key.newInstance(Container.class,
Container.DEFAULT_NAME), CONTAINER_FACTORY);
-
- // Inject the logger for the injected member's declaring class.
- factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME),
LOGGER_FACTORY);
}
/**
diff --git
a/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java
b/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java
index 07b7ececa..47027392b 100644
---
a/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java
+++
b/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java
@@ -19,8 +19,8 @@ package org.apache.struts2.inject.util;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
/**
* Starts a background thread that cleans up after reclaimed referents.
@@ -29,8 +29,7 @@ import java.util.logging.Logger;
*/
public class FinalizableReferenceQueue extends ReferenceQueue<Object> {
- private static final Logger logger =
- Logger.getLogger(FinalizableReferenceQueue.class.getName());
+ private static final Logger LOG =
LogManager.getLogger(FinalizableReferenceQueue.class);
private final AtomicReference<Thread> cleanupThread = new
AtomicReference<>();
@@ -45,7 +44,7 @@ public class FinalizableReferenceQueue extends
ReferenceQueue<Object> {
}
void deliverBadNews(Throwable t) {
- logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
+ LOG.error("Error cleaning up after reference.", t);
}
void start() {
diff --git
a/docs/superpowers/plans/2026-07-20-WW-5620-log4j2-logging-standardization.md
b/docs/superpowers/plans/2026-07-20-WW-5620-log4j2-logging-standardization.md
new file mode 100644
index 000000000..87442e449
--- /dev/null
+++
b/docs/superpowers/plans/2026-07-20-WW-5620-log4j2-logging-standardization.md
@@ -0,0 +1,345 @@
+# WW-5620 Log4j2 Logging Standardization Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development (recommended) or
superpowers:executing-plans to implement this plan task-by-task. Steps use
checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Migrate the three remaining legacy-logging source files to Log4j2
and remove the now-dead first-party SLF4J dependency declarations.
+
+**Architecture:** Three independent, mostly mechanical source edits (two
class-own logger swaps, one dead-DI-feature removal), followed by a pom cleanup
gated on a `mvn dependency:tree` check. No functional or security behavior
changes.
+
+**Tech Stack:** Java, Log4j2 (`org.apache.logging.log4j`), Maven. Core tests
are JUnit 4 + AssertJ + Mockito in this repo.
+
+## Global Constraints
+
+- Fix version: 7.3.0. Target ticket: WW-5620. Every commit message MUST be
prefixed `WW-5620`.
+- Logger convention: `private static final Logger LOG =
LogManager.getLogger(<Class>.class);` for static class loggers (see
`ActionSupport`, `ObjectFactory`). Preserve the existing field style where a
file already uses an instance logger.
+- No new pom dependency may be added. `log4j-api` is already available
(directly in core, transitively in tiles via `struts2-core`).
+- Do NOT touch the runtime bridges `log4j-jcl`, `log4j-slf4j-impl`, or
`commons-logging` — they route third-party logging and are out of scope.
+- Build/test commands: `mvn test -DskipAssembly -pl core` and `mvn test
-DskipAssembly -pl plugins/tiles`.
+- Reference spec:
`docs/superpowers/specs/2026-07-20-WW-5620-log4j2-logging-standardization-design.md`.
+
+---
+
+## File Structure
+
+- Modify:
`core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java`
— swap `java.util.logging` → Log4j2.
+- Modify:
`plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java`
— swap SLF4J → Log4j2.
+- Modify: `core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java`
— remove the injectable `java.util.logging.Logger` DI factory.
+- Modify: `core/pom.xml`, `parent/pom.xml`, `pom.xml` — remove dead
first-party SLF4J declarations.
+
+No test files are created: files 1–2 are behavior-preserving; file 3 removes
dead, untested code. Verification is via existing suites, `dependency:tree`,
and grep.
+
+---
+
+### Task 1: Migrate `FinalizableReferenceQueue` to Log4j2
+
+**Files:**
+- Modify:
`core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java`
+
+**Interfaces:**
+- Consumes: nothing from other tasks.
+- Produces: nothing consumed by other tasks (internal logger only).
+
+- [ ] **Step 1: Confirm current state**
+
+Run: `grep -n "logging\|logger\|Level"
core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java`
+Expected: lines showing `import java.util.logging.Level;`, `import
java.util.logging.Logger;`, the `logger` field
(`Logger.getLogger(...getName())`), and `logger.log(Level.SEVERE, "Error
cleaning up after reference.", t);`.
+
+- [ ] **Step 2: Replace the imports**
+
+Replace:
+```java
+import java.util.logging.Level;
+import java.util.logging.Logger;
+```
+with:
+```java
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+```
+(Keep these in import-order position consistent with the file; the surrounding
`java.lang.ref.*` / `java.util.concurrent.*` imports stay.)
+
+- [ ] **Step 3: Replace the logger field**
+
+Replace:
+```java
+ private static final Logger logger =
+ Logger.getLogger(FinalizableReferenceQueue.class.getName());
+```
+with:
+```java
+ private static final Logger LOG =
LogManager.getLogger(FinalizableReferenceQueue.class);
+```
+
+- [ ] **Step 4: Replace the log call**
+
+Replace:
+```java
+ logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
+```
+with:
+```java
+ LOG.error("Error cleaning up after reference.", t);
+```
+
+- [ ] **Step 5: Verify no leftover legacy references**
+
+Run: `grep -n "java.util.logging\|Level\.\|logger\b"
core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java`
+Expected: no matches.
+
+- [ ] **Step 6: Compile core**
+
+Run: `mvn -q -o compile -pl core -DskipAssembly` (drop `-o` if the local repo
needs downloads)
+Expected: BUILD SUCCESS.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add
core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java
+git commit -m "WW-5620 Migrate FinalizableReferenceQueue to Log4j2
+
+Co-Authored-By: Claude Opus 4.8 <[email protected]>"
+```
+
+---
+
+### Task 2: Migrate `AbstractDefaultToStringRenderable` to Log4j2
+
+**Files:**
+- Modify:
`plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java`
+
+**Interfaces:**
+- Consumes: nothing from other tasks.
+- Produces: nothing consumed by other tasks (internal instance logger only).
+
+- [ ] **Step 1: Confirm current state**
+
+Run: `grep -n "slf4j\|Logger\|log\."
plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java`
+Expected: `import org.slf4j.Logger;`, `import org.slf4j.LoggerFactory;`,
`private final Logger log = LoggerFactory.getLogger(getClass());`, and
`log.error("Error when closing a StringWriter, the impossible happened!", e);`.
+
+- [ ] **Step 2: Replace the imports**
+
+Replace:
+```java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+```
+with:
+```java
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+```
+(Keep import ordering consistent with the file; `org.apache.logging...` sorts
near the other `org.apache.*` imports.)
+
+- [ ] **Step 3: Replace the logger field factory call**
+
+Replace:
+```java
+ private final Logger log = LoggerFactory.getLogger(getClass());
+```
+with:
+```java
+ private final Logger log = LogManager.getLogger(getClass());
+```
+(The `log.error(...)` call is unchanged — the Log4j2 `Logger.error(String,
Throwable)` signature is call-compatible.)
+
+- [ ] **Step 4: Verify no leftover SLF4J references**
+
+Run: `grep -n "slf4j\|LoggerFactory"
plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java`
+Expected: no matches.
+
+- [ ] **Step 5: Compile the tiles plugin (confirms log4j-api resolves
transitively)**
+
+Run: `mvn -q -o compile -pl plugins/tiles -am -DskipAssembly` (drop `-o` if
downloads are needed)
+Expected: BUILD SUCCESS. If it fails with `package org.apache.logging.log4j
does not exist`, STOP — the transitive assumption is wrong; report back before
adding any dependency.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add
plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java
+git commit -m "WW-5620 Migrate AbstractDefaultToStringRenderable to Log4j2
+
+Co-Authored-By: Claude Opus 4.8 <[email protected]>"
+```
+
+---
+
+### Task 3: Remove the injectable Logger factory from `ContainerBuilder`
+
+**Files:**
+- Modify: `core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java`
+
+**Interfaces:**
+- Consumes: nothing from other tasks.
+- Produces: removes the DI binding for `java.util.logging.Logger` (verified
unused repo-wide).
+
+- [ ] **Step 1: Re-confirm the injectable Logger is unused**
+
+Run: `grep -rn --include="*.java" "java.util.logging.Logger" . | grep -v
/target/ | grep -v "ContainerBuilder.java\|FinalizableReferenceQueue.java"`
+Expected: no matches (after Task 1, `FinalizableReferenceQueue` no longer
appears either). If any consumer appears, STOP and report.
+
+- [ ] **Step 2: Remove the `LOGGER_FACTORY` constant**
+
+Delete this block:
+```java
+ private static final InternalFactory<Logger> LOGGER_FACTORY =
+ new InternalFactory<>() {
+ public Logger create(InternalContext context) {
+ Member member = context.getExternalContext().getMember();
+ return member == null ? Logger.getAnonymousLogger()
+ :
Logger.getLogger(member.getDeclaringClass().getName());
+ }
+
+ @Override
+ public Class<? extends Logger> type() {
+ return Logger.class;
+ }
+ };
+```
+
+- [ ] **Step 3: Remove the factory registration in the constructor**
+
+Delete these lines (comment + registration):
+```java
+ // Inject the logger for the injected member's declaring class.
+ factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME),
LOGGER_FACTORY);
+```
+Leave the preceding `CONTAINER_FACTORY` registration intact.
+
+- [ ] **Step 4: Remove the now-unused imports**
+
+Delete:
+```java
+import java.lang.reflect.Member;
+```
+and
+```java
+import java.util.logging.Logger;
+```
+
+- [ ] **Step 5: Update the class Javadoc**
+
+In the class Javadoc `<ul>`, delete the line:
+```java
+ * <li>Injects the {@link Logger} for the injected member's declaring class.
+```
+Leave the `<li>Injects the current {@link Container}.` bullet.
+
+- [ ] **Step 6: Verify no leftover references**
+
+Run: `grep -n "Logger\|Member\|LOGGER_FACTORY"
core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java`
+Expected: no matches.
+
+- [ ] **Step 7: Compile core**
+
+Run: `mvn -q -o compile -pl core -DskipAssembly` (drop `-o` if downloads are
needed)
+Expected: BUILD SUCCESS.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java
+git commit -m "WW-5620 Remove unused injectable j.u.l.Logger DI factory from
ContainerBuilder
+
+Co-Authored-By: Claude Opus 4.8 <[email protected]>"
+```
+
+---
+
+### Task 4: Remove dead first-party SLF4J dependency declarations
+
+**Files:**
+- Modify: `core/pom.xml`
+- Modify: `parent/pom.xml`
+- Modify: `pom.xml`
+
+**Interfaces:**
+- Consumes: Tasks 1–3 must be done first (no first-party source uses SLF4J
anymore).
+- Produces: nothing consumed by other tasks.
+
+- [ ] **Step 1: Dependency-tree gate (before deleting anything)**
+
+Run: `grep -rln --include="*.java" "org.slf4j" . | grep -v /target/`
+Expected: no matches (Task 2 removed the last usage).
+
+Run: `mvn -q -o dependency:tree -pl core,plugins/tiles -Dincludes=org.slf4j
2>&1 | grep -i slf4j` (drop `-o` if downloads are needed)
+Expected: any `org.slf4j:*` shown is transitive (an indented child of another
artifact), not a direct first-party declaration. If a first-party module
*requires* Struts to expose SLF4J for compilation, STOP, keep the minimum
needed, and note it in the spec's Verification section before continuing.
+
+- [ ] **Step 2: Remove the SLF4J block from `core/pom.xml`**
+
+Delete:
+```xml
+ <!-- SLF4J support -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+```
+
+- [ ] **Step 3: Remove the SLF4J `dependencyManagement` entries from
`parent/pom.xml`**
+
+Delete:
+```xml
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+```
+
+- [ ] **Step 4: Remove the `slf4j.version` property from root `pom.xml`**
+
+Delete:
+```xml
+ <slf4j.version>2.0.18</slf4j.version>
+```
+
+- [ ] **Step 5: Verify no SLF4J declarations remain**
+
+Run: `grep -rn "slf4j" --include="pom.xml" . | grep -v /target/`
+Expected: only bridge artifacts remain — `apps/showcase` `log4j-slf4j-impl`
(and its `<name>slf4j</name>` profile entry). No `org.slf4j:slf4j-api`, no
`slf4j-simple`, no `slf4j.version`.
+
+- [ ] **Step 6: Build and test core + tiles**
+
+Run: `mvn -o test -DskipAssembly -pl core -am` (drop `-o` if downloads are
needed)
+Expected: BUILD SUCCESS, tests green.
+
+Run: `mvn -o test -DskipAssembly -pl plugins/tiles -am`
+Expected: BUILD SUCCESS, tests green.
+
+If any test fails because it silently relied on `slf4j-simple` as a test
logging backend, STOP and report — do not add unrelated logging config to force
a pass.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add core/pom.xml parent/pom.xml pom.xml
+git commit -m "WW-5620 Remove dead first-party SLF4J dependency declarations
+
+Co-Authored-By: Claude Opus 4.8 <[email protected]>"
+```
+
+---
+
+## Self-Review
+
+**Spec coverage:**
+- Spec §1 FinalizableReferenceQueue → Task 1. ✓
+- Spec §2 AbstractDefaultToStringRenderable → Task 2. ✓
+- Spec §3 ContainerBuilder factory removal → Task 3. ✓
+- Spec §4 dependency cleanup (core/parent/root poms) → Task 4. ✓
+- Spec Verification (dependency-tree gate, compile+test, grep) → Task 4 Step
1, per-task compile steps, grep steps. ✓
+- Out-of-scope bridges left untouched → asserted in Global Constraints and
Task 4 Step 5. ✓
+
+**Placeholder scan:** No TBD/TODO/"handle edge cases"/vague steps. Every code
step shows exact before/after. ✓
+
+**Type consistency:** Logger field named `LOG` (static, Task 1) vs `log`
(instance, Task 2) — intentional, matches each file's existing style and the
codebase convention; no cross-task symbol sharing. ✓
diff --git
a/docs/superpowers/specs/2026-07-20-WW-5620-log4j2-logging-standardization-design.md
b/docs/superpowers/specs/2026-07-20-WW-5620-log4j2-logging-standardization-design.md
new file mode 100644
index 000000000..db5b619ef
--- /dev/null
+++
b/docs/superpowers/specs/2026-07-20-WW-5620-log4j2-logging-standardization-design.md
@@ -0,0 +1,135 @@
+# WW-5620 — Standardize logging on Log4j2
+
+- **Jira:** [WW-5620](https://issues.apache.org/jira/browse/WW-5620)
+- **Type:** Improvement (refactor / dependency cleanup)
+- **Fix version:** 7.3.0
+- **Components:** Core, Plugin - Tiles
+- **Date:** 2026-07-20
+
+## Goal
+
+Log4j2 is the project's standard logging layer. Three source files still use
+legacy logging frameworks. Migrate them to Log4j2 and remove the now-dead
+first-party SLF4J dependency declarations that only existed to support them.
+
+This is a pure logging-standardization refactor: **no functional or security
+behavior changes.** All three files are vendored (files 1 and 3 from Google
+Guice, file 2 from Apache Tiles), so there is no upstream-sync concern.
+
+## Scope
+
+### In scope
+1. Migrate `FinalizableReferenceQueue.java` (core) off `java.util.logging`.
+2. Migrate `AbstractDefaultToStringRenderable.java` (tiles) off SLF4J.
+3. Remove the injectable `java.util.logging.Logger` DI factory from
+ `ContainerBuilder.java` (core).
+4. Remove the first-party SLF4J dependency declarations that become dead after
+ step 2, gated on a build/dependency-tree verification.
+
+### Out of scope
+- The commons-logging → Log4j2 and SLF4J → Log4j2 **runtime bridges**
+ (`log4j-jcl`, `log4j-slf4j-impl`, `commons-logging`). These route
+ *transitive third-party* library logging into Log4j2 and are unrelated to
+ first-party code. They stay.
+- Any broader logging refactor beyond these three files.
+
+## File-level changes
+
+### 1.
`core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java`
+
+Faithful class-own logger swap, adopting the codebase convention (`private
+static final Logger LOG = LogManager.getLogger(<Class>.class);` — see
+`ActionSupport`, `ObjectFactory`, `DefaultActionInvocation`).
+
+- Remove imports `java.util.logging.Level`, `java.util.logging.Logger`.
+- Add imports `org.apache.logging.log4j.LogManager`,
+ `org.apache.logging.log4j.Logger`.
+- Replace
+ `private static final Logger logger =
Logger.getLogger(FinalizableReferenceQueue.class.getName());`
+ with
+ `private static final Logger LOG =
LogManager.getLogger(FinalizableReferenceQueue.class);`
+- Replace
+ `logger.log(Level.SEVERE, "Error cleaning up after reference.", t);`
+ with
+ `LOG.error("Error cleaning up after reference.", t);`
+
+### 2.
`plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java`
+
+Faithful SLF4J swap. The Log4j2 `Logger` error API is call-compatible, so only
+imports and the factory call change.
+
+- Remove imports `org.slf4j.Logger`, `org.slf4j.LoggerFactory`.
+- Add imports `org.apache.logging.log4j.LogManager`,
+ `org.apache.logging.log4j.Logger`.
+- Replace `private final Logger log = LoggerFactory.getLogger(getClass());`
+ with `private final Logger log = LogManager.getLogger(getClass());`
+- `log.error("Error when closing a StringWriter, the impossible happened!",
e);`
+ is unchanged.
+- No new pom dependency: `log4j-api` reaches the tiles plugin transitively via
+ `struts2-core`. Confirm at build time.
+
+### 3. `core/src/main/java/org/apache/struts2/inject/ContainerBuilder.java`
+
+Here `java.util.logging.Logger` is **not** a class-own logger — it is
+registered as an injectable DI type (a legacy Guice feature: consumers could
+`@Inject` a `Logger` scoped to their declaring class). A repo-wide search found
+**no code that injects it**. Per decision, remove the dead feature rather than
+port it to Log4j2 (which has no `getAnonymousLogger()` equivalent for the
+`member == null` fallback and would change the DI contract).
+
+- Remove the `LOGGER_FACTORY` `InternalFactory<Logger>` constant.
+- Remove its registration:
+ `factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME),
LOGGER_FACTORY);`
+ and the preceding comment.
+- Remove now-unused imports `java.util.logging.Logger` and
+ `java.lang.reflect.Member` (`Member` is used only inside the removed
factory).
+- Update the class Javadoc: drop the
+ "Injects the `{@link Logger}` for the injected member's declaring class"
+ bullet, leaving the `Container` bullet.
+
+### 4. Dependency cleanup (gated on verification)
+
+Evidence for removal:
+- The tiles file (step 2) is the only main-source SLF4J user in the repo.
+- No test source uses SLF4J; there is no `simplelogger.properties`.
+- core's `slf4j-api` is `optional` (never part of the transitive API contract)
+ and dates to the original xwork merge.
+
+Remove, after the verification gate below:
+- `core/pom.xml` — the optional `slf4j-api` dependency and the test-scoped
+ `slf4j-simple` dependency (the `<!-- SLF4J support -->` block).
+- `parent/pom.xml` — the `dependencyManagement` entries for `slf4j-api` and
+ `slf4j-simple`.
+- root `pom.xml` — the `<slf4j.version>` property.
+
+`java.util.logging` is JDK built-in — nothing to remove for the two core files.
+
+## Verification
+
+1. **Dependency-tree gate (do before deleting pom entries):**
+ run `mvn dependency:tree` for `core` and `plugins/tiles`. Confirm no
+ first-party module *requires* Struts to expose SLF4J. If something genuinely
+ needs it, keep that minimum and record the reason here.
+2. **Compile + test:**
+ - `mvn test -DskipAssembly -pl core` (core tests are JUnit 4 in this repo)
+ - `mvn test -DskipAssembly -pl plugins/tiles`
+3. **Grep confirmation:** no source references the injectable `Logger` type,
+ the removed `logger` field, or `org.slf4j` after the change.
+
+## Testing strategy
+
+No new tests are required:
+- Files 1 and 2 are behavior-preserving logger swaps.
+- File 3 removes dead, untested code.
+
+Existing core and tiles test suites must stay green.
+
+## Risks
+
+- **Low.** The only non-mechanical change is removing the injectable `Logger`
+ factory in `ContainerBuilder`, which is unused internally. A third-party
+ extension that injected `java.util.logging.Logger` from the Struts container
+ would lose that binding; this is acceptable for the 7.3.0 minor and called
out
+ here.
+- The dependency-tree gate protects against an unexpected transitive reliance
on
+ the SLF4J declarations.
diff --git a/parent/pom.xml b/parent/pom.xml
index 063269182..59defe45a 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -274,17 +274,6 @@
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>${slf4j.version}</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- <version>${slf4j.version}</version>
- </dependency>
-
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
diff --git
a/plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java
b/plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java
index 7498bb5d2..be211e0ef 100644
---
a/plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java
+++
b/plugins/tiles/src/main/java/org/apache/tiles/velocity/template/AbstractDefaultToStringRenderable.java
@@ -26,14 +26,14 @@ import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.apache.tiles.velocity.TilesVelocityException;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.Renderable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Renderable that provides a default implementation of Renderable#toString()
@@ -74,7 +74,7 @@ public abstract class AbstractDefaultToStringRenderable
implements Renderable {
/**
* The logging object.
*/
- private final Logger log = LoggerFactory.getLogger(getClass());
+ private final Logger log = LogManager.getLogger(getClass());
/**
* Constructor.
diff --git a/pom.xml b/pom.xml
index d9a188a54..d471e8e77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -125,7 +125,6 @@
<log4j2.version>2.26.1</log4j2.version>
<mockito.version>5.23.0</mockito.version>
<ognl.version>3.4.11</ognl.version>
- <slf4j.version>2.0.18</slf4j.version>
<spring.version>6.2.12</spring.version>
<struts-annotations.version>2.0</struts-annotations.version>
<velocity-tools.version>3.1</velocity-tools.version>