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>

Reply via email to