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-intellij-plugin.git
The following commit(s) were added to refs/heads/main by this push:
new 948df5d Add @StrutsParameter missing-annotation inspection (#109)
948df5d is described below
commit 948df5d88546ba72cc59e8d24fad2bd78f24b208
Author: Lukasz Lenart <[email protected]>
AuthorDate: Thu Jul 2 16:42:33 2026 +0200
Add @StrutsParameter missing-annotation inspection (#109)
* docs: add StrutsParameter inspection design
Document the first Java inspection slice for identifying Struts action
members that need @StrutsParameter when annotation-based binding is enabled.
Co-authored-by: Cursor <[email protected]>
* docs: add StrutsParameter inspection implementation plan
Break the Java inspection work into test-first tasks covering fixtures,
utilities, inspection registration, and verification.
Co-authored-by: Cursor <[email protected]>
* test: cover StrutsParameter annotation inspection
Co-authored-by: Cursor <[email protected]>
* test: use non-action name in StrutsParameter inspection test
Co-authored-by: Cursor <[email protected]>
* feat: add StrutsParameter inspection utilities
Co-authored-by: Cursor <[email protected]>
* feat: inspect missing StrutsParameter annotations
Co-authored-by: Cursor <[email protected]>
* test: create StrutsParameter inspection fixtures with project files
Co-authored-by: Cursor <[email protected]>
* test: register StrutsParameter fixtures as struts.xml
Co-authored-by: Cursor <[email protected]>
* fix: parse Struts requireAnnotations constant as string
Co-authored-by: Cursor <[email protected]>
* docs: add changelog entry for StrutsParameter inspection
Co-authored-by: Cursor <[email protected]>
* test: avoid shadowing Struts file set helper overload
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
---
CHANGELOG.md | 1 +
.../2026-07-02-struts-parameter-inspection.md | 806 +++++++++++++++++++++
...026-07-02-struts-parameter-inspection-design.md | 196 +++++
.../java/com/intellij/struts2/StrutsConstants.java | 4 +
.../struts2/inspection/StrutsActionClassUtil.java | 89 +++
.../StrutsParameterAnnotationInspection.java | 73 ++
.../inspection/StrutsParameterAnnotationUtil.java | 52 ++
.../inspection/StrutsParameterConfigUtil.java | 65 ++
.../contributor/StrutsCoreConstantContributor.java | 8 +-
src/main/resources/META-INF/plugin.xml | 4 +
.../resources/messages/Struts2Bundle.properties | 3 +
.../struts2/BasicLightHighlightingTestCase.java | 22 +
.../StrutsParameterAnnotationInspectionTest.java | 161 ++++
.../strutsParameter/struts-disable-annotations.xml | 32 +
.../strutsParameter/struts-require-annotations.xml | 32 +
15 files changed, 1547 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b7270a..8a1a56b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
### Added
+- Add Java inspection for Struts action setters and public fields missing
`@StrutsParameter` when annotation-based parameter binding is required
- Diagram tab auto-refreshes when `struts.xml` is edited (same file, active
tab) and on tab activation after Text edits
([#97](https://github.com/apache/struts-intellij-plugin/issues/97))
### Removed
diff --git a/docs/superpowers/plans/2026-07-02-struts-parameter-inspection.md
b/docs/superpowers/plans/2026-07-02-struts-parameter-inspection.md
new file mode 100644
index 0000000..aa5afa8
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-02-struts-parameter-inspection.md
@@ -0,0 +1,806 @@
+# StrutsParameter Java Inspection 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:** Add a Java inspection that warns when Struts action setters or
public fields will not bind because `struts.parameters.requireAnnotations=true`
and `@StrutsParameter` is missing.
+
+**Architecture:** Implement a focused `LocalInspectionTool` for Java files.
The inspection delegates action-class recognition, annotation/member checks,
and configuration checks to small utilities so later quick-fixes or
getter/depth validation can build on the same foundation.
+
+**Tech Stack:** IntelliJ Platform PSI/inspection APIs, Struts facet/model
APIs, JUnit 3 light fixture tests, Gradle.
+
+---
+
+## File Structure
+
+- Create
`src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java`
+ - Java inspection entry point and PSI visitor.
+- Create
`src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java`
+ - Shared action-class predicate used by the inspection.
+- Create
`src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java`
+ - `@StrutsParameter` availability, annotation, setter, and field checks.
+- Create
`src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java`
+ - Determines whether annotation-based parameter binding is active.
+- Modify `src/main/java/com/intellij/struts2/StrutsConstants.java`
+ - Add the `@StrutsParameter` FQN constant.
+- Modify
`src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java`
+ - Add a typed constant key for `struts.parameters.requireAnnotations`.
+- Modify `src/main/resources/META-INF/plugin.xml`
+ - Register the Java inspection.
+- Modify `src/main/resources/messages/Struts2Bundle.properties`
+ - Add inspection display name and warning message.
+- Create
`src/test/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspectionTest.java`
+ - Focused inspection tests.
+- Create
`src/test/testData/inspection/strutsParameter/struts-require-annotations.xml`
+ - Struts config with `requireAnnotations=true` and one mapped action.
+- Create
`src/test/testData/inspection/strutsParameter/struts-disable-annotations.xml`
+ - Struts config with `requireAnnotations=false` and one mapped action.
+- Modify `CHANGELOG.md`
+ - Add an unreleased entry for the inspection.
+
+---
+
+### Task 1: Add Failing Inspection Tests
+
+**Files:**
+- Create:
`src/test/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspectionTest.java`
+- Create:
`src/test/testData/inspection/strutsParameter/struts-require-annotations.xml`
+- Create:
`src/test/testData/inspection/strutsParameter/struts-disable-annotations.xml`
+
+- [ ] **Step 1: Add Struts config fixture with required annotations**
+
+Create
`src/test/testData/inspection/strutsParameter/struts-require-annotations.xml`:
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
+ "https://struts.apache.org/dtds/struts-6.0.dtd">
+
+<struts>
+ <constant name="struts.parameters.requireAnnotations" value="true"/>
+
+ <package name="default" extends="struts-default">
+ <action name="sample" class="test.SampleAction"/>
+ </package>
+</struts>
+```
+
+- [ ] **Step 2: Add Struts config fixture with disabled annotations**
+
+Create
`src/test/testData/inspection/strutsParameter/struts-disable-annotations.xml`:
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
+ "https://struts.apache.org/dtds/struts-6.0.dtd">
+
+<struts>
+ <constant name="struts.parameters.requireAnnotations" value="false"/>
+
+ <package name="default" extends="struts-default">
+ <action name="sample" class="test.SampleAction"/>
+ </package>
+</struts>
+```
+
+- [ ] **Step 3: Add the inspection test class**
+
+Create
`src/test/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspectionTest.java`:
+
+```java
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInspection.InspectionProfileEntry;
+import com.intellij.struts2.BasicLightHighlightingTestCase;
+import org.jetbrains.annotations.NotNull;
+
+public class StrutsParameterAnnotationInspectionTest extends
BasicLightHighlightingTestCase {
+ private static final String WARNING =
+ "Parameter injection requires @StrutsParameter when
struts.parameters.requireAnnotations is enabled";
+
+ @Override
+ protected InspectionProfileEntry[] getHighlightingInspections() {
+ return new InspectionProfileEntry[]{new
StrutsParameterAnnotationInspection()};
+ }
+
+ @NotNull
+ @Override
+ protected String getTestDataLocation() {
+ return "inspection/strutsParameter/";
+ }
+
+ public void testWarnsAboutUnannotatedSetterAndPublicField() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSet("struts-require-annotations.xml");
+
+ myFixture.configureByText("test/SampleAction.java", """
+ package test;
+
+ public class SampleAction {
+ public String <warning descr="%s">username</warning>;
+
+ public void <warning descr="%s">setPassword</warning>(String password)
{
+ }
+ }
+ """.formatted(WARNING, WARNING));
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnAboutAnnotatedMembers() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSet("struts-require-annotations.xml");
+
+ myFixture.configureByText("test/SampleAction.java", """
+ package test;
+
+ import org.apache.struts2.interceptor.parameter.StrutsParameter;
+
+ public class SampleAction {
+ @StrutsParameter
+ public String username;
+
+ @StrutsParameter
+ public void setPassword(String password) {
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnWhenRequireAnnotationsIsFalse() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSet("struts-disable-annotations.xml");
+
+ myFixture.configureByText("test/SampleAction.java", """
+ package test;
+
+ public class SampleAction {
+ public String username;
+
+ public void setPassword(String password) {
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnInNonActionClass() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSet("struts-require-annotations.xml");
+
+ myFixture.configureByText("test/NotAction.java", """
+ package test;
+
+ public class NotAction {
+ public String username;
+
+ public void setPassword(String password) {
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnAboutNonPublicMembersOrGetters() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSet("struts-require-annotations.xml");
+
+ myFixture.configureByText("test/SampleAction.java", """
+ package test;
+
+ public class SampleAction {
+ private String privateField;
+ protected String protectedField;
+ String packagePrivateField;
+ public static String staticField;
+
+ private void setPrivateValue(String value) {
+ }
+
+ protected void setProtectedValue(String value) {
+ }
+
+ void setPackagePrivateValue(String value) {
+ }
+
+ public static void setStaticValue(String value) {
+ }
+
+ public String getUser() {
+ return "";
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ private void configureStrutsParameterAnnotation() {
+
myFixture.configureByText("org/apache/struts2/interceptor/parameter/StrutsParameter.java",
"""
+ package org.apache.struts2.interceptor.parameter;
+
+ public @interface StrutsParameter {
+ int depth() default 0;
+ }
+ """);
+ }
+}
+```
+
+- [ ] **Step 4: Run the failing test**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests "StrutsParameterAnnotationInspectionTest"
--no-configuration-cache
+```
+
+Expected: compilation fails because `StrutsParameterAnnotationInspection` does
not exist.
+
+- [ ] **Step 5: Commit the red tests**
+
+```bash
+git add
src/test/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspectionTest.java
\
+ src/test/testData/inspection/strutsParameter/struts-require-annotations.xml \
+ src/test/testData/inspection/strutsParameter/struts-disable-annotations.xml
+git commit -m "$(cat <<'EOF'
+test: cover StrutsParameter annotation inspection
+
+EOF
+)"
+```
+
+---
+
+### Task 2: Add Constants And Utility Classes
+
+**Files:**
+- Modify: `src/main/java/com/intellij/struts2/StrutsConstants.java`
+- Modify:
`src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java`
+- Create:
`src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java`
+- Create:
`src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java`
+- Create:
`src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java`
+
+- [ ] **Step 1: Add the annotation FQN constant**
+
+In `src/main/java/com/intellij/struts2/StrutsConstants.java`, add this
constant near the other plugin-wide class-name constants:
+
+```java
+ @NonNls
+ public static final String STRUTS_PARAMETER_ANNOTATION =
+ "org.apache.struts2.interceptor.parameter.StrutsParameter";
+```
+
+- [ ] **Step 2: Add a typed key for `struts.parameters.requireAnnotations`**
+
+In
`src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java`,
add this public key after `ACTION_EXTENSION`:
+
+```java
+ /**
+ * {@code struts.parameters.requireAnnotations}.
+ */
+ public static final StrutsConstantKey<Boolean> REQUIRE_ANNOTATIONS =
StrutsConstantKey.create(
+ "struts.parameters.requireAnnotations");
+```
+
+Then replace the string literal in the constant list:
+
+```java
+ addBooleanProperty(REQUIRE_ANNOTATIONS.getKey()),
+```
+
+- [ ] **Step 3: Add action-class detection utility**
+
+Create
`src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java`:
+
+```java
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.InheritanceUtil;
+import com.intellij.struts2.StrutsConstants;
+import com.intellij.struts2.dom.struts.model.StrutsManager;
+import com.intellij.struts2.dom.struts.model.StrutsModel;
+import com.intellij.struts2.model.jam.convention.StrutsConventionConstants;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+final class StrutsActionClassUtil {
+ private StrutsActionClassUtil() {
+ }
+
+ static boolean isActionClass(@NotNull PsiClass psiClass) {
+ if (!isConcretePublicClass(psiClass)) {
+ return false;
+ }
+
+ final Module module = ModuleUtilCore.findModuleForPsiElement(psiClass);
+ if (module == null) {
+ return false;
+ }
+
+ final StrutsModel strutsModel =
StrutsManager.getInstance(psiClass.getProject()).getCombinedModel(module);
+ if (strutsModel != null && strutsModel.isActionClass(psiClass)) {
+ return true;
+ }
+
+ if (AnnotationUtil.isAnnotated(psiClass, StrutsConventionConstants.ACTION,
0) ||
+ AnnotationUtil.isAnnotated(psiClass,
StrutsConventionConstants.ACTIONS, 0)) {
+ return true;
+ }
+
+ if (!isConventionPluginPresent(psiClass)) {
+ return false;
+ }
+
+ final String className = psiClass.getName();
+ if (className != null && StringUtil.endsWith(className, "Action")) {
+ return true;
+ }
+
+ return InheritanceUtil.isInheritor(psiClass,
StrutsConstants.XWORK_ACTION_CLASS);
+ }
+
+ private static boolean isConcretePublicClass(@Nullable PsiClass psiClass) {
+ return psiClass != null &&
+ !psiClass.isInterface() &&
+ !psiClass.isEnum() &&
+ !psiClass.isAnnotationType() &&
+ psiClass.hasModifierProperty(PsiModifier.PUBLIC) &&
+ !psiClass.hasModifierProperty(PsiModifier.ABSTRACT);
+ }
+
+ private static boolean isConventionPluginPresent(@NotNull PsiElement
element) {
+ final Module module = ModuleUtilCore.findModuleForPsiElement(element);
+ if (module == null) {
+ return false;
+ }
+
+ final GlobalSearchScope scope =
GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false);
+ return JavaPsiFacade.getInstance(element.getProject())
+ .findClass(StrutsConventionConstants.CONVENTIONS_SERVICE, scope)
!= null;
+ }
+}
+```
+
+- [ ] **Step 4: Add annotation/member utility**
+
+Create
`src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java`:
+
+```java
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.PsiModifierListOwner;
+import com.intellij.psi.util.PropertyUtilBase;
+import com.intellij.struts2.StrutsConstants;
+import org.jetbrains.annotations.NotNull;
+
+final class StrutsParameterAnnotationUtil {
+ private StrutsParameterAnnotationUtil() {
+ }
+
+ static boolean isStrutsParameterAvailable(@NotNull PsiElement context) {
+ return JavaPsiFacade.getInstance(context.getProject())
+ .findClass(StrutsConstants.STRUTS_PARAMETER_ANNOTATION,
context.getResolveScope()) != null;
+ }
+
+ static boolean isAnnotatedWithStrutsParameter(@NotNull PsiModifierListOwner
owner) {
+ return AnnotationUtil.isAnnotated(owner,
StrutsConstants.STRUTS_PARAMETER_ANNOTATION, 0);
+ }
+
+ static boolean isInjectableSetter(@NotNull PsiMethod method) {
+ return method.hasModifierProperty(PsiModifier.PUBLIC) &&
+ !method.hasModifierProperty(PsiModifier.STATIC) &&
+ !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
+ PropertyUtilBase.isSimplePropertySetter(method);
+ }
+
+ static boolean isInjectableField(@NotNull PsiField field) {
+ return field.hasModifierProperty(PsiModifier.PUBLIC) &&
+ !field.hasModifierProperty(PsiModifier.STATIC);
+ }
+}
+```
+
+- [ ] **Step 5: Add configuration utility**
+
+Create
`src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java`:
+
+```java
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.struts2.facet.ui.StrutsVersionDetector;
+import com.intellij.struts2.model.constant.StrutsConstantManager;
+import
com.intellij.struts2.model.constant.contributor.StrutsCoreConstantContributor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+final class StrutsParameterConfigUtil {
+ private StrutsParameterConfigUtil() {
+ }
+
+ static boolean isRequireAnnotationsEnabled(@NotNull PsiElement context) {
+ final PsiFile containingFile = context.getContainingFile();
+ if (containingFile == null) {
+ return false;
+ }
+
+ final Boolean configuredValue =
StrutsConstantManager.getInstance(context.getProject())
+ .getConvertedValue(containingFile,
StrutsCoreConstantContributor.REQUIRE_ANNOTATIONS);
+ if (configuredValue != null) {
+ return configuredValue;
+ }
+
+ final Module module = ModuleUtilCore.findModuleForPsiElement(context);
+ if (module == null) {
+ return false;
+ }
+
+ return isStruts7OrNewer(StrutsVersionDetector.detectStrutsVersion(module));
+ }
+
+ static boolean isStruts7OrNewer(@Nullable String version) {
+ if (version == null || version.isBlank()) {
+ return false;
+ }
+
+ final int firstDot = version.indexOf('.');
+ final String majorVersion = firstDot == -1 ? version :
version.substring(0, firstDot);
+ try {
+ return Integer.parseInt(majorVersion) >= 7;
+ }
+ catch (NumberFormatException e) {
+ return false;
+ }
+ }
+}
+```
+
+- [ ] **Step 6: Run compilation-targeted tests**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests "StrutsParameterAnnotationInspectionTest"
--no-configuration-cache
+```
+
+Expected: compilation still fails because
`StrutsParameterAnnotationInspection` does not exist, but the new utilities
compile.
+
+- [ ] **Step 7: Commit constants and utilities**
+
+```bash
+git add src/main/java/com/intellij/struts2/StrutsConstants.java \
+
src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java
\
+ src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java \
+
src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java
\
+ src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java
+git commit -m "$(cat <<'EOF'
+feat: add StrutsParameter inspection utilities
+
+EOF
+)"
+```
+
+---
+
+### Task 3: Implement And Register The Inspection
+
+**Files:**
+- Create:
`src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java`
+- Modify: `src/main/resources/META-INF/plugin.xml`
+- Modify: `src/main/resources/messages/Struts2Bundle.properties`
+
+- [ ] **Step 1: Add bundle messages**
+
+In `src/main/resources/messages/Struts2Bundle.properties`, add these keys near
the other inspection keys:
+
+```properties
+inspections.struts.parameter.annotation.display.name=Missing StrutsParameter
annotation
+inspections.struts.parameter.annotation.message=Parameter injection requires
@StrutsParameter when struts.parameters.requireAnnotations is enabled
+```
+
+- [ ] **Step 2: Add the inspection class**
+
+Create
`src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java`:
+
+```java
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInspection.LocalInspectionTool;
+import com.intellij.codeInspection.ProblemsHolder;
+import com.intellij.psi.JavaElementVisitor;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiIdentifier;
+import com.intellij.psi.PsiJavaFile;
+import com.intellij.psi.PsiMethod;
+import com.intellij.struts2.StrutsBundle;
+import com.intellij.struts2.facet.StrutsFacet;
+import org.jetbrains.annotations.NotNull;
+
+public final class StrutsParameterAnnotationInspection extends
LocalInspectionTool {
+ @Override
+ public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder
holder, boolean isOnTheFly) {
+ if (!(holder.getFile() instanceof PsiJavaFile) ||
+ StrutsFacet.getInstance(holder.getFile()) == null ||
+
!StrutsParameterAnnotationUtil.isStrutsParameterAvailable(holder.getFile()) ||
+
!StrutsParameterConfigUtil.isRequireAnnotationsEnabled(holder.getFile())) {
+ return new PsiElementVisitor() {
+ };
+ }
+
+ return new JavaElementVisitor() {
+ @Override
+ public void visitClass(@NotNull PsiClass psiClass) {
+ if (!StrutsActionClassUtil.isActionClass(psiClass)) {
+ return;
+ }
+
+ for (PsiMethod method : psiClass.getMethods()) {
+ if (StrutsParameterAnnotationUtil.isInjectableSetter(method) &&
+
!StrutsParameterAnnotationUtil.isAnnotatedWithStrutsParameter(method)) {
+ registerProblem(method.getNameIdentifier());
+ }
+ }
+
+ for (PsiField field : psiClass.getFields()) {
+ if (StrutsParameterAnnotationUtil.isInjectableField(field) &&
+
!StrutsParameterAnnotationUtil.isAnnotatedWithStrutsParameter(field)) {
+ registerProblem(field.getNameIdentifier());
+ }
+ }
+ }
+
+ private void registerProblem(PsiIdentifier identifier) {
+ if (identifier == null) {
+ return;
+ }
+
+ holder.registerProblem(identifier,
+
StrutsBundle.message("inspections.struts.parameter.annotation.message"));
+ }
+ };
+ }
+}
+```
+
+- [ ] **Step 3: Register the Java inspection**
+
+In `src/main/resources/META-INF/plugin.xml`, add this entry after
`HardcodedActionUrlInspection`:
+
+```xml
+ <localInspection language="JAVA" groupPath="Struts"
shortName="StrutsParameterAnnotation" applyToDialects="false"
+ bundle="messages.Struts2Bundle"
key="inspections.struts.parameter.annotation.display.name"
+ groupKey="inspections.group.display.name"
enabledByDefault="true" level="WARNING"
+
implementationClass="com.intellij.struts2.inspection.StrutsParameterAnnotationInspection"/>
+```
+
+- [ ] **Step 4: Run the targeted inspection tests**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests "StrutsParameterAnnotationInspectionTest"
--no-configuration-cache
+```
+
+Expected: all tests in `StrutsParameterAnnotationInspectionTest` pass.
+
+- [ ] **Step 5: Commit inspection implementation**
+
+```bash
+git add
src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java
\
+ src/main/resources/META-INF/plugin.xml \
+ src/main/resources/messages/Struts2Bundle.properties
+git commit -m "$(cat <<'EOF'
+feat: inspect missing StrutsParameter annotations
+
+EOF
+)"
+```
+
+---
+
+### Task 4: Final Verification And Changelog
+
+**Files:**
+- Modify: `CHANGELOG.md`
+
+- [ ] **Step 1: Add changelog entry**
+
+In `CHANGELOG.md`, add this under `[Unreleased]` → `### Added`:
+
+```markdown
+- Add Java inspection for Struts action setters and public fields missing
`@StrutsParameter` when annotation-based parameter binding is required
+```
+
+- [ ] **Step 2: Run targeted tests**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests "StrutsParameterAnnotationInspectionTest"
--no-configuration-cache
+```
+
+Expected: build succeeds and the inspection tests pass.
+
+- [ ] **Step 3: Run the broader suite**
+
+Run:
+
+```bash
+./gradlew test -x rat --no-configuration-cache
+```
+
+Expected: build succeeds.
+
+- [ ] **Step 4: Check IDE lints for edited files**
+
+Use the IDE diagnostics for these files:
+
+```text
+src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java
+src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java
+src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java
+src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java
+src/main/java/com/intellij/struts2/StrutsConstants.java
+src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java
+```
+
+Expected: no newly introduced errors.
+
+- [ ] **Step 5: Commit changelog and verification-ready state**
+
+```bash
+git add CHANGELOG.md
+git commit -m "$(cat <<'EOF'
+docs: add changelog entry for StrutsParameter inspection
+
+EOF
+)"
+```
+
+- [ ] **Step 6: Review final branch state**
+
+Run:
+
+```bash
+git status --short
+git log --oneline -8
+```
+
+Expected: working tree is clean and the latest commits are the spec, tests,
utilities, inspection, and changelog.
diff --git
a/docs/superpowers/specs/2026-07-02-struts-parameter-inspection-design.md
b/docs/superpowers/specs/2026-07-02-struts-parameter-inspection-design.md
new file mode 100644
index 0000000..42b2fed
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-02-struts-parameter-inspection-design.md
@@ -0,0 +1,196 @@
+# StrutsParameter Java Inspection
+
+**Date:** 2026-07-02
+**Status:** Draft for review
+
+## Problem
+
+Struts 7.2.x defaults to requiring `@StrutsParameter` for request parameter
injection. When `struts.parameters.requireAnnotations=true`, public setters and
public fields on action classes no longer receive request values unless they
are explicitly annotated with
`org.apache.struts2.interceptor.parameter.StrutsParameter`.
+
+The plugin now suggests the Struts 7.2.1 constants, but it does not help users
find action members that will stop binding after migration. This leaves a
common upgrade failure mode invisible until runtime.
+
+## Goals
+
+1. Add a Java inspection that warns about action injection points missing
`@StrutsParameter`.
+2. Run the inspection only when annotation-based parameter binding is active.
+3. Keep the first implementation focused on likely request injection points:
public setters and public instance fields.
+4. Reuse existing Struts facet, action detection, and constant resolution
patterns where possible.
+5. Avoid quick-fixes, depth validation, and XML/OGNL cross-file analysis in
this first slice.
+
+## Non-Goals
+
+- Add a quick-fix to insert `@StrutsParameter`.
+- Validate getter `depth` values.
+- Validate `struts.xml`, JSP, or OGNL property paths against annotations.
+- Inspect `struts.chaining.requireAnnotations` behavior.
+- Dynamically infer every possible runtime action mapping.
+
+## Decision
+
+Implement a focused Java `LocalInspectionTool` for action classes.
+
+The inspection will report public JavaBean setters and public non-static
fields in Struts action classes when those members lack `@StrutsParameter` and
parameter annotations are required. This gives users a practical migration
signal while keeping the feature small enough to review and release
independently.
+
+## Alternatives Considered
+
+### Reference-driven inspection
+
+Only warn on properties that are referenced from `struts.xml`, JSP tags, form
fields, or OGNL expressions.
+
+Rejected for the first slice because the plugin does not currently resolve
OGNL property paths to action members, and a reference-driven approach would
miss runtime request parameters not represented in project files.
+
+### Getter and depth validation
+
+Inspect getters annotated with `@StrutsParameter(depth = ...)` and compare the
depth against nested request property paths.
+
+Deferred because it requires cross-file property-path analysis and a more
nuanced model of collection, map, and object traversal. It is a good follow-up
after the basic migration inspection exists.
+
+### Quick-fix first
+
+Offer an intention to add `@StrutsParameter` wherever the inspection reports a
problem.
+
+Deferred because adding the annotation has security implications. The first
slice should identify likely migration issues without automatically broadening
the request-binding surface.
+
+## Inspection Scope
+
+### Enabled Context
+
+The inspection runs for Java files only when all of these are true:
+
+1. The file belongs to a module with a Struts facet.
+2. `org.apache.struts2.interceptor.parameter.StrutsParameter` is available in
the resolve scope.
+3. Parameter annotations are required.
+
+Parameter annotations are considered required when:
+
+- `struts.parameters.requireAnnotations` resolves to `true`, or
+- the project uses Struts 7+ and the constant is not explicitly set.
+
+If the constant resolves to `false`, the inspection should not report missing
annotations. If the plugin cannot determine whether annotations are required,
it should stay quiet to avoid noisy warnings in older Struts projects.
+
+### Action Class Detection
+
+The inspection should reuse the same broad action-class signals already used
in the plugin:
+
+- classes referenced as actions in the Struts model, when available;
+- convention actions marked with `@Action` or `@Actions`;
+- public non-abstract classes ending with `Action` when the Convention plugin
is present;
+- public non-abstract classes inheriting from `com.opensymphony.xwork2.Action`.
+
+Shared action-detection helpers should be extracted if needed so the
inspection does not duplicate the logic from
`StrutsConventionImplicitUsageProvider`.
+
+### Reported Members
+
+Report a problem on the member name when a member is a likely direct injection
point and lacks `@StrutsParameter`.
+
+Reported members:
+
+- public, non-static, non-abstract setter methods with one parameter;
+- public, non-static fields.
+
+Ignored members:
+
+- getters;
+- constructors;
+- static, abstract, private, protected, or package-private members;
+- action execution methods such as `execute()`;
+- members already annotated with `@StrutsParameter`;
+- members declared outside the inspected action class, including inherited
framework members.
+
+Getter support is intentionally excluded from v1 because a useful warning for
getters needs to understand nested property paths and `depth`.
+
+## User Experience
+
+Inspection group: `Struts`
+Language: `JAVA`
+Suggested short name: `StrutsParameterAnnotation`
+
+Example message:
+
+> Parameter injection requires `@StrutsParameter` when
`struts.parameters.requireAnnotations` is enabled.
+
+The inspection should explain that request parameters will not bind to the
member without the annotation. It should not claim the annotation is always
safe to add.
+
+## Components
+
+### `StrutsParameterAnnotationInspection`
+
+New Java `LocalInspectionTool` that creates a `JavaElementVisitor`.
+
+Responsibilities:
+
+- skip files without Struts support or without required annotation mode;
+- visit candidate `PsiClass` instances;
+- report missing annotations on candidate methods and fields.
+
+### `StrutsActionClassUtil`
+
+New utility for action-class recognition if extracting shared logic keeps the
inspection simple.
+
+It can reuse predicates from `StrutsConventionImplicitUsageProvider` and add
Struts model lookup later if needed.
+
+### `StrutsParameterAnnotationUtil`
+
+Small utility for annotation and member checks:
+
+- `isStrutsParameterAvailable(PsiElement)`;
+- `isAnnotatedWithStrutsParameter(PsiModifierListOwner)`;
+- `isInjectableSetter(PsiMethod)`;
+- `isInjectableField(PsiField)`.
+
+### `StrutsParameterConfigUtil`
+
+Small utility for checking whether `struts.parameters.requireAnnotations` is
active for the current file.
+
+It should call `StrutsConstantManager.getConvertedValue(...)` for the new
constant key. If a Struts 7+ version can be detected from the module library,
use default `true` when no explicit constant exists.
+
+## Data Flow
+
+```text
+Java file
+ |
+ v
+Struts facet present?
+ |
+ v
+@StrutsParameter available and requireAnnotations active?
+ |
+ v
+PsiClass is a Struts action?
+ |
+ v
+public setter/public field lacks @StrutsParameter
+ |
+ v
+inspection warning
+```
+
+## Testing
+
+Add focused inspection tests using `BasicLightHighlightingTestCase`.
+
+Core test cases:
+
+1. Public setter on an action class without `@StrutsParameter` is warned when
`struts.parameters.requireAnnotations=true`.
+2. Public field on an action class without `@StrutsParameter` is warned when
`struts.parameters.requireAnnotations=true`.
+3. Annotated setter and annotated field are not warned.
+4. Unannotated setter is not warned when
`struts.parameters.requireAnnotations=false`.
+5. Non-action classes are not warned.
+6. Private/protected/package-private setters and fields are not warned.
+7. Getter methods are not warned in v1.
+
+Fixtures should include a lightweight
`org.apache.struts2.interceptor.parameter.StrutsParameter` test stub if the
current test dependency does not provide the annotation.
+
+Run targeted tests first, then the broader suite:
+
+```bash
+./gradlew test -x rat --tests "StrutsParameterAnnotationInspectionTest"
--no-configuration-cache
+./gradlew test -x rat --no-configuration-cache
+```
+
+## Follow-Up
+
+- Add a quick-fix to insert `@StrutsParameter` after the warning text and
security caveats are settled.
+- Add getter and `depth` validation based on actual nested property paths.
+- Validate XML/JSP/OGNL parameter paths against annotated action members.
+- Consider support for `struts.chaining.requireAnnotations` after direct
request-parameter support is stable.
diff --git a/src/main/java/com/intellij/struts2/StrutsConstants.java
b/src/main/java/com/intellij/struts2/StrutsConstants.java
index 773aa03..26903c9 100644
--- a/src/main/java/com/intellij/struts2/StrutsConstants.java
+++ b/src/main/java/com/intellij/struts2/StrutsConstants.java
@@ -40,6 +40,10 @@ public final class StrutsConstants {
@NonNls
public static final String XWORK_ACTION_CLASS =
"com.opensymphony.xwork2.Action";
+ @NonNls
+ public static final String STRUTS_PARAMETER_ANNOTATION =
+ "org.apache.struts2.interceptor.parameter.StrutsParameter";
+
/**
* Spring object factory.
*/
diff --git
a/src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java
b/src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java
new file mode 100644
index 0000000..2f2b2f2
--- /dev/null
+++ b/src/main/java/com/intellij/struts2/inspection/StrutsActionClassUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.InheritanceUtil;
+import com.intellij.struts2.StrutsConstants;
+import com.intellij.struts2.dom.struts.model.StrutsManager;
+import com.intellij.struts2.dom.struts.model.StrutsModel;
+import com.intellij.struts2.model.jam.convention.StrutsConventionConstants;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+final class StrutsActionClassUtil {
+ private StrutsActionClassUtil() {
+ }
+
+ static boolean isActionClass(@NotNull PsiClass psiClass) {
+ if (!isConcretePublicClass(psiClass)) {
+ return false;
+ }
+
+ final Module module = ModuleUtilCore.findModuleForPsiElement(psiClass);
+ if (module == null) {
+ return false;
+ }
+
+ final StrutsModel strutsModel =
StrutsManager.getInstance(psiClass.getProject()).getCombinedModel(module);
+ if (strutsModel != null && strutsModel.isActionClass(psiClass)) {
+ return true;
+ }
+
+ if (AnnotationUtil.isAnnotated(psiClass, StrutsConventionConstants.ACTION,
0) ||
+ AnnotationUtil.isAnnotated(psiClass,
StrutsConventionConstants.ACTIONS, 0)) {
+ return true;
+ }
+
+ if (!isConventionPluginPresent(psiClass)) {
+ return false;
+ }
+
+ final String className = psiClass.getName();
+ if (className != null && StringUtil.endsWith(className, "Action")) {
+ return true;
+ }
+
+ return InheritanceUtil.isInheritor(psiClass,
StrutsConstants.XWORK_ACTION_CLASS);
+ }
+
+ private static boolean isConcretePublicClass(@Nullable PsiClass psiClass) {
+ return psiClass != null &&
+ !psiClass.isInterface() &&
+ !psiClass.isEnum() &&
+ !psiClass.isAnnotationType() &&
+ psiClass.hasModifierProperty(PsiModifier.PUBLIC) &&
+ !psiClass.hasModifierProperty(PsiModifier.ABSTRACT);
+ }
+
+ private static boolean isConventionPluginPresent(@NotNull PsiElement
element) {
+ final Module module = ModuleUtilCore.findModuleForPsiElement(element);
+ if (module == null) {
+ return false;
+ }
+
+ final GlobalSearchScope scope =
GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false);
+ return JavaPsiFacade.getInstance(element.getProject())
+ .findClass(StrutsConventionConstants.CONVENTIONS_SERVICE, scope)
!= null;
+ }
+}
diff --git
a/src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java
b/src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java
new file mode 100644
index 0000000..7a113b5
--- /dev/null
+++
b/src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspection.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInspection.LocalInspectionTool;
+import com.intellij.codeInspection.ProblemsHolder;
+import com.intellij.psi.JavaElementVisitor;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiIdentifier;
+import com.intellij.psi.PsiJavaFile;
+import com.intellij.psi.PsiMethod;
+import com.intellij.struts2.StrutsBundle;
+import com.intellij.struts2.facet.StrutsFacet;
+import org.jetbrains.annotations.NotNull;
+
+public final class StrutsParameterAnnotationInspection extends
LocalInspectionTool {
+ @Override
+ public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder
holder, boolean isOnTheFly) {
+ if (!(holder.getFile() instanceof PsiJavaFile) ||
+ StrutsFacet.getInstance(holder.getFile()) == null ||
+
!StrutsParameterAnnotationUtil.isStrutsParameterAvailable(holder.getFile()) ||
+
!StrutsParameterConfigUtil.isRequireAnnotationsEnabled(holder.getFile())) {
+ return new PsiElementVisitor() {
+ };
+ }
+
+ return new JavaElementVisitor() {
+ @Override
+ public void visitClass(@NotNull PsiClass psiClass) {
+ if (!StrutsActionClassUtil.isActionClass(psiClass)) {
+ return;
+ }
+
+ for (PsiMethod method : psiClass.getMethods()) {
+ if (StrutsParameterAnnotationUtil.isInjectableSetter(method) &&
+
!StrutsParameterAnnotationUtil.isAnnotatedWithStrutsParameter(method)) {
+ registerProblem(method.getNameIdentifier());
+ }
+ }
+
+ for (PsiField field : psiClass.getFields()) {
+ if (StrutsParameterAnnotationUtil.isInjectableField(field) &&
+
!StrutsParameterAnnotationUtil.isAnnotatedWithStrutsParameter(field)) {
+ registerProblem(field.getNameIdentifier());
+ }
+ }
+ }
+
+ private void registerProblem(PsiIdentifier identifier) {
+ if (identifier == null) {
+ return;
+ }
+
+ holder.registerProblem(identifier,
+
StrutsBundle.message("inspections.struts.parameter.annotation.message"));
+ }
+ };
+ }
+}
diff --git
a/src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java
b/src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java
new file mode 100644
index 0000000..df68aec
--- /dev/null
+++
b/src/main/java/com/intellij/struts2/inspection/StrutsParameterAnnotationUtil.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.PsiModifierListOwner;
+import com.intellij.psi.util.PropertyUtilBase;
+import com.intellij.struts2.StrutsConstants;
+import org.jetbrains.annotations.NotNull;
+
+final class StrutsParameterAnnotationUtil {
+ private StrutsParameterAnnotationUtil() {
+ }
+
+ static boolean isStrutsParameterAvailable(@NotNull PsiElement context) {
+ return JavaPsiFacade.getInstance(context.getProject())
+ .findClass(StrutsConstants.STRUTS_PARAMETER_ANNOTATION,
context.getResolveScope()) != null;
+ }
+
+ static boolean isAnnotatedWithStrutsParameter(@NotNull PsiModifierListOwner
owner) {
+ return AnnotationUtil.isAnnotated(owner,
StrutsConstants.STRUTS_PARAMETER_ANNOTATION, 0);
+ }
+
+ static boolean isInjectableSetter(@NotNull PsiMethod method) {
+ return method.hasModifierProperty(PsiModifier.PUBLIC) &&
+ !method.hasModifierProperty(PsiModifier.STATIC) &&
+ !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
+ PropertyUtilBase.isSimplePropertySetter(method);
+ }
+
+ static boolean isInjectableField(@NotNull PsiField field) {
+ return field.hasModifierProperty(PsiModifier.PUBLIC) &&
+ !field.hasModifierProperty(PsiModifier.STATIC);
+ }
+}
diff --git
a/src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java
b/src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java
new file mode 100644
index 0000000..d79fa1a
--- /dev/null
+++
b/src/main/java/com/intellij/struts2/inspection/StrutsParameterConfigUtil.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.struts2.facet.ui.StrutsVersionDetector;
+import com.intellij.struts2.model.constant.StrutsConstantManager;
+import
com.intellij.struts2.model.constant.contributor.StrutsCoreConstantContributor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+final class StrutsParameterConfigUtil {
+ private StrutsParameterConfigUtil() {
+ }
+
+ static boolean isRequireAnnotationsEnabled(@NotNull PsiElement context) {
+ final PsiFile containingFile = context.getContainingFile();
+ if (containingFile == null) {
+ return false;
+ }
+
+ final String configuredValue =
StrutsConstantManager.getInstance(context.getProject())
+ .getConvertedValue(containingFile,
StrutsCoreConstantContributor.REQUIRE_ANNOTATIONS);
+ if (configuredValue != null) {
+ return Boolean.parseBoolean(configuredValue.trim());
+ }
+
+ final Module module = ModuleUtilCore.findModuleForPsiElement(context);
+ if (module == null) {
+ return false;
+ }
+
+ return isStruts7OrNewer(StrutsVersionDetector.detectStrutsVersion(module));
+ }
+
+ static boolean isStruts7OrNewer(@Nullable String version) {
+ if (version == null || version.isBlank()) {
+ return false;
+ }
+
+ final int firstDot = version.indexOf('.');
+ final String majorVersion = firstDot == -1 ? version :
version.substring(0, firstDot);
+ try {
+ return Integer.parseInt(majorVersion) >= 7;
+ }
+ catch (NumberFormatException e) {
+ return false;
+ }
+ }
+}
diff --git
a/src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java
b/src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java
index 5b5b8e2..5658242 100644
---
a/src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java
+++
b/src/main/java/com/intellij/struts2/model/constant/contributor/StrutsCoreConstantContributor.java
@@ -47,6 +47,12 @@ public final class StrutsCoreConstantContributor extends
StrutsConstantContribut
public static final StrutsConstantKey<List<String>> ACTION_EXTENSION =
StrutsConstantKey.create(
"struts.action.extension");
+ /**
+ * {@code struts.parameters.requireAnnotations}.
+ */
+ public static final StrutsConstantKey<String> REQUIRE_ANNOTATIONS =
StrutsConstantKey.create(
+ "struts.parameters.requireAnnotations");
+
@NonNls
private static final List<StrutsConstant> CONSTANTS = Arrays.asList(
addClassWithShortcutProperty("struts.configuration", ""),
@@ -101,7 +107,7 @@ public final class StrutsCoreConstantContributor extends
StrutsConstantContribut
addClassWithShortcutProperty("struts.unknownHandlerManager",
"com.opensymphony.xwork2.UnknownHandlerManager"),
addBooleanProperty("struts.ognl.allowStaticMethodAccess"),
- addBooleanProperty("struts.parameters.requireAnnotations"),
+ addBooleanProperty(REQUIRE_ANNOTATIONS.getKey()),
addBooleanProperty("struts.parameters.requireAnnotations.transitionMode"),
addBooleanProperty("struts.chaining.requireAnnotations")
);
diff --git a/src/main/resources/META-INF/plugin.xml
b/src/main/resources/META-INF/plugin.xml
index b516e26..f7a92c5 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -103,6 +103,10 @@
bundle="messages.Struts2Bundle"
key="inspections.hardcoded.action.url.display.name"
groupKey="inspections.group.display.name"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.struts2.jsp.inspection.HardcodedActionUrlInspection"/>
+ <localInspection language="JAVA" groupPath="Struts"
shortName="StrutsParameterAnnotation" applyToDialects="false"
+ bundle="messages.Struts2Bundle"
key="inspections.struts.parameter.annotation.display.name"
+ groupKey="inspections.group.display.name"
enabledByDefault="true" level="WARNING"
+
implementationClass="com.intellij.struts2.inspection.StrutsParameterAnnotationInspection"/>
<standardResourceProvider
implementation="com.intellij.struts2.Struts2ResourceProvider"/>
diff --git a/src/main/resources/messages/Struts2Bundle.properties
b/src/main/resources/messages/Struts2Bundle.properties
index d2800f6..b9064e7 100644
--- a/src/main/resources/messages/Struts2Bundle.properties
+++ b/src/main/resources/messages/Struts2Bundle.properties
@@ -64,6 +64,9 @@
inspections.validator.config.model.display.name=Validator-Config model
inspections.hardcoded.action.url.display.name=Hardcoded action URL
+inspections.struts.parameter.annotation.display.name=Missing StrutsParameter
annotation
+inspections.struts.parameter.annotation.message=Parameter injection requires
@StrutsParameter when struts.parameters.requireAnnotations is enabled
+
intentions.family.name=Struts 2
structure.view.filter.params=Hide Params
diff --git
a/src/test/java/com/intellij/struts2/BasicLightHighlightingTestCase.java
b/src/test/java/com/intellij/struts2/BasicLightHighlightingTestCase.java
index 79d6e46..d3cee42 100644
--- a/src/test/java/com/intellij/struts2/BasicLightHighlightingTestCase.java
+++ b/src/test/java/com/intellij/struts2/BasicLightHighlightingTestCase.java
@@ -172,4 +172,26 @@ public abstract class BasicLightHighlightingTestCase
extends LightJavaCodeInsigh
final Set<StrutsFileSet> strutsFileSetSet =
facetConfiguration.getFileSets();
strutsFileSetSet.add(fileSet);
}
+
+ /**
+ * Copies a source fixture to a specific project path and registers it in a
new Struts file set.
+ * Useful when the constant resolution requires a specific file name (e.g.
{@code struts.xml}).
+ *
+ * @param targetPath project-relative path to copy the fixture to
+ * @param sourcePath fixture path inside the test data directory
+ */
+ protected void createStrutsFileSetFromFixture(@NonNls String targetPath,
@NonNls String sourcePath) {
+ final StrutsFacet strutsFacet = StrutsFacet.getInstance(getModule());
+ assertNotNull(strutsFacet);
+ final StrutsFacetConfiguration facetConfiguration =
strutsFacet.getConfiguration();
+
+ final StrutsFileSet fileSet = new StrutsFileSet("test", "test",
facetConfiguration);
+ myStrutsFileSets.add(fileSet);
+
+ final VirtualFile file = myFixture.copyFileToProject(sourcePath,
targetPath);
+ assertNotNull("could not find file: '" + sourcePath + "'", file);
+ fileSet.addFile(file);
+
+ facetConfiguration.getFileSets().add(fileSet);
+ }
}
\ No newline at end of file
diff --git
a/src/test/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspectionTest.java
b/src/test/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspectionTest.java
new file mode 100644
index 0000000..ff6f0c5
--- /dev/null
+++
b/src/test/java/com/intellij/struts2/inspection/StrutsParameterAnnotationInspectionTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2026 The authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.struts2.inspection;
+
+import com.intellij.codeInspection.InspectionProfileEntry;
+import com.intellij.psi.PsiFile;
+import com.intellij.struts2.BasicLightHighlightingTestCase;
+import org.jetbrains.annotations.NotNull;
+
+public class StrutsParameterAnnotationInspectionTest extends
BasicLightHighlightingTestCase {
+ private static final String WARNING =
+ "Parameter injection requires @StrutsParameter when
struts.parameters.requireAnnotations is enabled";
+
+ @Override
+ protected InspectionProfileEntry[] getHighlightingInspections() {
+ return new InspectionProfileEntry[]{new
StrutsParameterAnnotationInspection()};
+ }
+
+ @NotNull
+ @Override
+ protected String getTestDataLocation() {
+ return "inspection/strutsParameter/";
+ }
+
+ public void testWarnsAboutUnannotatedSetterAndPublicField() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSetFromFixture("struts.xml",
"struts-require-annotations.xml");
+
+ configureFileForHighlighting("test/SampleAction.java", """
+ package test;
+
+ public class SampleAction {
+ public String <warning descr="%s">username</warning>;
+
+ public void <warning descr="%s">setPassword</warning>(String password)
{
+ }
+ }
+ """.formatted(WARNING, WARNING));
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnAboutAnnotatedMembers() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSetFromFixture("struts.xml",
"struts-require-annotations.xml");
+
+ configureFileForHighlighting("test/SampleAction.java", """
+ package test;
+
+ import org.apache.struts2.interceptor.parameter.StrutsParameter;
+
+ public class SampleAction {
+ @StrutsParameter
+ public String username;
+
+ @StrutsParameter
+ public void setPassword(String password) {
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnWhenRequireAnnotationsIsFalse() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSetFromFixture("struts.xml",
"struts-disable-annotations.xml");
+
+ configureFileForHighlighting("test/SampleAction.java", """
+ package test;
+
+ public class SampleAction {
+ public String username;
+
+ public void setPassword(String password) {
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnInNonActionClass() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSetFromFixture("struts.xml",
"struts-require-annotations.xml");
+
+ configureFileForHighlighting("test/UserService.java", """
+ package test;
+
+ public class UserService {
+ public String username;
+
+ public void setPassword(String password) {
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ public void testDoesNotWarnAboutNonPublicMembersOrGetters() {
+ configureStrutsParameterAnnotation();
+ createStrutsFileSetFromFixture("struts.xml",
"struts-require-annotations.xml");
+
+ configureFileForHighlighting("test/SampleAction.java", """
+ package test;
+
+ public class SampleAction {
+ private String privateField;
+ protected String protectedField;
+ String packagePrivateField;
+ public static String staticField;
+
+ private void setPrivateValue(String value) {
+ }
+
+ protected void setProtectedValue(String value) {
+ }
+
+ void setPackagePrivateValue(String value) {
+ }
+
+ public static void setStaticValue(String value) {
+ }
+
+ public String getUser() {
+ return "";
+ }
+ }
+ """);
+
+ myFixture.checkHighlighting();
+ }
+
+ private void configureStrutsParameterAnnotation() {
+
myFixture.addFileToProject("org/apache/struts2/interceptor/parameter/StrutsParameter.java",
"""
+ package org.apache.struts2.interceptor.parameter;
+
+ public @interface StrutsParameter {
+ int depth() default 0;
+ }
+ """);
+ }
+
+ private void configureFileForHighlighting(@NotNull String path, @NotNull
String text) {
+ final PsiFile psiFile = myFixture.addFileToProject(path, text);
+ myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile());
+ }
+}
diff --git
a/src/test/testData/inspection/strutsParameter/struts-disable-annotations.xml
b/src/test/testData/inspection/strutsParameter/struts-disable-annotations.xml
new file mode 100644
index 0000000..bcb15e4
--- /dev/null
+++
b/src/test/testData/inspection/strutsParameter/struts-disable-annotations.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
+ "https://struts.apache.org/dtds/struts-6.0.dtd">
+
+<struts>
+ <constant name="struts.parameters.requireAnnotations" value="false"/>
+
+ <package name="default" extends="struts-default">
+ <action name="sample" class="test.SampleAction"/>
+ </package>
+</struts>
diff --git
a/src/test/testData/inspection/strutsParameter/struts-require-annotations.xml
b/src/test/testData/inspection/strutsParameter/struts-require-annotations.xml
new file mode 100644
index 0000000..62cf347
--- /dev/null
+++
b/src/test/testData/inspection/strutsParameter/struts-require-annotations.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
+ "https://struts.apache.org/dtds/struts-6.0.dtd">
+
+<struts>
+ <constant name="struts.parameters.requireAnnotations" value="true"/>
+
+ <package name="default" extends="struts-default">
+ <action name="sample" class="test.SampleAction"/>
+ </package>
+</struts>