Skip to content

SONARJAVA-6758: Fix FPs in S5673 for controllers without mappings and redundant annotations - #5925

Open
romainbrenguier wants to merge 2 commits into
masterfrom
romain/sonarjava-6758
Open

SONARJAVA-6758: Fix FPs in S5673 for controllers without mappings and redundant annotations#5925
romainbrenguier wants to merge 2 commits into
masterfrom
romain/sonarjava-6758

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

Summary

  • Controllers without mapping: Before suggesting @Controller or @RestController, the rule now verifies the class has at least one method with a request mapping annotation (@RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping). Classes named "Controller" that don't handle HTTP requests no longer trigger the rule.
  • Non-web framework exclusions: Classes implementing ApplicationRunner, CommandLineRunner, HealthIndicator, or ReactiveHealthIndicator, or annotated with @Endpoint, @RestControllerEndpoint, or @ControllerEndpoint are excluded from Controller/RestController suggestions.
  • Redundant annotations: If a class already has a specialized stereotype annotation (@Controller, @RestController, @Service, @Repository) alongside @Component, the rule no longer raises.

Test plan

  • Updated test sample with noncompliant Controller/RestController cases that include request mapping methods
  • Added compliant cases for controllers without request mapping methods
  • Added compliant cases for controllers implementing non-web framework interfaces (ApplicationRunner, CommandLineRunner)
  • Added compliant cases for redundant stereotype annotations
  • All Spring-related tests pass (45 tests)

🤖 Generated with Claude Code

… redundant annotations

- Require request mapping annotations before suggesting @Controller/@RestController
- Skip raising when a specialized stereotype annotation is already present alongside @component
- Exclude classes implementing non-web framework interfaces (ApplicationRunner, CommandLineRunner, HealthIndicator)
- Exclude classes annotated with actuator endpoint annotations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

SONARJAVA-6758

Comment on lines +101 to +112
private static boolean hasRequestMappingMethod(ClassTree classTree) {
for (Tree member : classTree.members()) {
if (member instanceof MethodTree method) {
for (AnnotationTree annotation : method.modifiers().annotations()) {
if (REQUEST_MAPPING_ANNOTATIONS.contains(annotation.annotationType().symbolType().fullyQualifiedName())) {
return true;
}
}
}
}
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Edge Case: Class-level @RequestMapping not detected as a mapping method

hasRequestMappingMethod only inspects method-level annotations, so a controller that declares its request mapping at the class level (e.g. @component @RequestMapping("/api") with handler methods that carry no mapping annotation, or methods inherited from a base class) will no longer be flagged. This trades the fixed FPs for potential false negatives on legitimate controllers. Consider also checking for a class-level @RequestMapping / mapping annotation before concluding the class handles no HTTP requests.

Was this helpful? React with 👍 / 👎

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class-level @RequestMapping alone is not a handler in Spring MVC (it only sets a path prefix), so I would not treat that as a required change.

Inherited mappings are a real gap: hasRequestMappingMethod only looks at classTree.members(), so a @Component subclass of a base controller with @GetMapping methods would now be an FN. Please walk superTypes() and use isAnnotatedWith — I left a review comment on that method.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on both points. I'm updating hasRequestMappingMethod to also walk superclasses (via the semantic model, checking each ancestor's method symbols for a mapping annotation) so inherited handler methods are still detected, without treating a class-level @RequestMapping as a handler by itself.

Extract duplicated "Controller" and "RestController" string literals
into constants to fix S1192 issues. Add test cases for HealthIndicator,
ReactiveHealthIndicator, and @endpoint to improve coverage on new code
above 90% threshold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqube-next

Copy link
Copy Markdown

@romainbrenguier
romainbrenguier marked this pull request as ready for review August 14, 2026 08:35

@nathsou nathsou left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RSPEC is now inconsistent with the analyzer

The ticket also asks to improve S5673 RSPEC (it was judged not actionable). There is no RSPEC PR, and the published examples are now wrong.

rules/S5673/java/rule.adoc still shows an empty FooBarRestController as noncompliant. After this change that class would not raise, which is why the unit test had to add @GetMapping to keep it noncompliant.

Please open a sibling RSPEC PR that:

  • puts a mapping method on the Controller/RestController noncompliant example
  • documents the new exceptions (no mapping methods, already-specialized stereotypes, actuator/startup types)
  • aligns the issue message with the implementation (“or rename this type if @Component is intentional”)

Comment on lines +104 to +115
private static boolean hasRequestMappingMethod(ClassTree classTree) {
for (Tree member : classTree.members()) {
if (member instanceof MethodTree method) {
for (AnnotationTree annotation : method.modifiers().annotations()) {
if (REQUEST_MAPPING_ANNOTATIONS.contains(annotation.annotationType().symbolType().fullyQualifiedName())) {
return true;
}
}
}
}
return false;
}

@nathsou nathsou Aug 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only inspects classTree.members(), so mappings declared on a superclass or implemented interface are missed. That is a common Spring pattern and a new FN:

public abstract class BaseController {
  @GetMapping("/status")
  public String status() { return "ok"; }
}

@Component // FN after this PR; previously raised
public class StatusController extends BaseController {
}

memberSymbols() is also declaration-only. Walking classTree.symbol().superTypes() (as SpringRequestMappingMethodCheck already does) and checking method.metadata().isAnnotatedWith(...) would also catch inherited and composed mapping annotations.

Comment on lines +58 to +62
private static final List<String> NON_WEB_FRAMEWORK_ANNOTATIONS = List.of(
"org.springframework.boot.actuate.endpoint.annotation.Endpoint",
"org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint",
"org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RestControllerEndpoint and @ControllerEndpoint are in the exclusion list but untested. Those are the cases where this extra exclusion actually matters: they can carry @GetMapping and must not be rewritten to @Controller / @RestController.

Please add sample classes for both. The @Endpoint / HealthIndicator / ApplicationRunner cases are already covered, and those exclusions are mostly redundant with the mapping-method guard (the original FPs had no mapping annotations).

@gitar-bot

gitar-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 0 resolved / 1 findings

Refines S5673 rule logic to prevent false positives on unmapped controllers and redundant annotations. Consider updating hasRequestMappingMethod to also inspect class-level @RequestMapping annotations.

💡 Edge Case: Class-level @RequestMapping not detected as a mapping method

📄 java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java:101-112

hasRequestMappingMethod only inspects method-level annotations, so a controller that declares its request mapping at the class level (e.g. @Component @RequestMapping("/api") with handler methods that carry no mapping annotation, or methods inherited from a base class) will no longer be flagged. This trades the fixed FPs for potential false negatives on legitimate controllers. Consider also checking for a class-level @RequestMapping / mapping annotation before concluding the class handles no HTTP requests.

🤖 Prompt for agents
Code Review: Refines S5673 rule logic to prevent false positives on unmapped controllers and redundant annotations. Consider updating hasRequestMappingMethod to also inspect class-level @RequestMapping annotations.

1. 💡 Edge Case: Class-level @RequestMapping not detected as a mapping method
   Files: java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java:101-112

   hasRequestMappingMethod only inspects method-level annotations, so a controller that declares its request mapping at the class level (e.g. @Component @RequestMapping("/api") with handler methods that carry no mapping annotation, or methods inherited from a base class) will no longer be flagged. This trades the fixed FPs for potential false negatives on legitimate controllers. Consider also checking for a class-level @RequestMapping / mapping annotation before concluding the class handles no HTTP requests.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants