From ebd2db55013f63e641c6cad1bdba4c2a7e94a0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 30 Jun 2026 16:23:13 +0200 Subject: [PATCH 1/4] implement check --- .../JpaEntityFinalCheckJakartaSample.java | 45 +++++++++++ .../JpaEntityFinalCheckJavaxSample.java | 33 ++++++++ .../java/checks/JpaEntityFinalCheck.java | 57 ++++++++++++++ .../java/checks/JpaEntityFinalCheckTest.java | 41 ++++++++++ .../org/sonar/l10n/java/rules/java/S8947.html | 76 +++++++++++++++++++ .../org/sonar/l10n/java/rules/java/S8947.json | 26 +++++++ .../java/rules/java/Sonar_way_profile.json | 1 + 7 files changed, 279 insertions(+) create mode 100644 java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java create mode 100644 java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/JpaEntityFinalCheckTest.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.json diff --git a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java new file mode 100644 index 00000000000..5da0d34b986 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java @@ -0,0 +1,45 @@ +package checks; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; + +@Entity +final class JpaEntityFinalCheckJakartaFinalEntity { // Noncompliant {{Remove this "final" modifier from this JPA entity class.}} +//^[sc=1;ec=5] + @Id + private Long id; + + public Long getId() { + return id; + } +} + +@MappedSuperclass +final class JpaEntityFinalCheckJakartaFinalMappedSuperclass { // Noncompliant {{Remove this "final" modifier from this JPA entity class.}} +//^[sc=1;ec=5] + @Id + private Long id; +} + +@Entity +class JpaEntityFinalCheckJakartaCompliantEntity { // Compliant + @Id + private Long id; + + public Long getId() { + return id; + } +} + +@MappedSuperclass +class JpaEntityFinalCheckJakartaCompliantMappedSuperclass { // Compliant + @Id + private Long id; +} + +class JpaEntityFinalCheckJakartaNotAnEntity { // Compliant - not a JPA entity +} + +final class JpaEntityFinalCheckJakartaFinalNotAnEntity { // Compliant - not a JPA entity +} diff --git a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java new file mode 100644 index 00000000000..c61adc93c15 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java @@ -0,0 +1,33 @@ +package checks; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +@Entity +final class JpaEntityFinalCheckJavaxFinalEntity { // Noncompliant {{Remove this "final" modifier from this JPA entity class.}} +//^[sc=1;ec=5] + @Id + private Long id; + + public Long getId() { + return id; + } +} + +@MappedSuperclass +final class JpaEntityFinalCheckJavaxFinalMappedSuperclass { // Noncompliant {{Remove this "final" modifier from this JPA entity class.}} +//^[sc=1;ec=5] + @Id + private Long id; +} + +@Entity +class JpaEntityFinalCheckJavaxCompliantEntity { // Compliant + @Id + private Long id; + + public Long getId() { + return id; + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java b/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java new file mode 100644 index 00000000000..250561fd8bb --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java @@ -0,0 +1,57 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.List; +import org.sonar.check.Rule; +import org.sonar.java.model.ModifiersUtils; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.SymbolMetadata; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.Modifier; +import org.sonar.plugins.java.api.tree.ModifierKeywordTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S8947") +public class JpaEntityFinalCheck extends IssuableSubscriptionVisitor { + + private static final List ENTITY_ANNOTATIONS = List.of( + "javax.persistence.Entity", + "jakarta.persistence.Entity", + "javax.persistence.MappedSuperclass", + "jakarta.persistence.MappedSuperclass" + ); + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.CLASS); + } + + @Override + public void visitNode(Tree tree) { + ClassTree classTree = (ClassTree) tree; + SymbolMetadata metadata = classTree.symbol().metadata(); + if (ENTITY_ANNOTATIONS.stream().noneMatch(metadata::isAnnotatedWith)) { + return; + } + + ModifierKeywordTree finalClassModifier = ModifiersUtils.getModifier(classTree.modifiers(), Modifier.FINAL); + if (finalClassModifier != null) { + reportIssue(finalClassModifier, "Remove this \"final\" modifier from this JPA entity class."); + } + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/JpaEntityFinalCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/JpaEntityFinalCheckTest.java new file mode 100644 index 00000000000..847f1ac0d39 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/JpaEntityFinalCheckTest.java @@ -0,0 +1,41 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class JpaEntityFinalCheckTest { + + @Test + void testWithJakarta() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/JpaEntityFinalCheckJakartaSample.java")) + .withCheck(new JpaEntityFinalCheck()) + .verifyIssues(); + } + + @Test + void testWithJavax() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/JpaEntityFinalCheckJavaxSample.java")) + .withCheck(new JpaEntityFinalCheck()) + .verifyIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html new file mode 100644 index 00000000000..215896da70f --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html @@ -0,0 +1,76 @@ +

This issue arises when Java classes annotated with @Entity or @MappedSuperclass are declared final, or when +methods within these classes are declared final. JPA providers require the ability to create proxy subclasses for lazy loading and other +runtime optimizations, which is prevented by the final modifier.

+

Why is this an issue?

+

JPA (Java Persistence API) providers like Hibernate rely on runtime proxy generation to implement several key features:

+
    +
  • Lazy loading: Loading associated entities only when they are actually accessed, rather than eagerly fetching everything + upfront
  • +
  • Dirty checking: Tracking which fields have changed to optimize database updates
  • +
  • Performance optimizations: Creating lightweight proxies that defer expensive operations
  • +
+

To create these proxies, the JPA provider needs to generate a subclass of your entity class at runtime. This subclass overrides methods to add the +lazy loading and tracking behavior.

+

When you declare a class or method as final, you prevent inheritance and method overriding. This breaks the proxy mechanism:

+
    +
  • A final class cannot be subclassed, so no proxy can be created
  • +
  • A final method cannot be overridden, so the JPA provider cannot intercept calls to implement lazy loading
  • +
+

Without working proxies, lazy loading fails. Instead of loading data on demand, the JPA provider may fall back to eager loading, which can cause +significant performance problems. In some cases, it may even cause runtime exceptions.

+

What is the potential impact?

+

When JPA entities or their methods are marked as final, the application can experience:

+
    +
  • Performance degradation: Lazy loading fails, forcing the application to eagerly load entire object graphs. This can result in + loading hundreds or thousands of unnecessary records from the database, significantly slowing down queries.
  • +
  • Increased memory consumption: Eagerly loading large object graphs consumes excessive memory, potentially leading to + OutOfMemoryErrors in production.
  • +
  • Runtime exceptions: Some JPA providers throw exceptions when they cannot create required proxies, causing application + failures.
  • +
  • Unpredictable behavior: The application may work correctly in development or testing (with small datasets) but fail or perform + poorly in production (with realistic data volumes).
  • +
+

How to fix it

+

Remove the final modifier from the entity class declaration. This allows the JPA provider to create proxy subclasses for lazy loading +and other optimizations.

+

Code examples

+

Noncompliant code example

+
+@Entity
+public final class User { // Noncompliant
+    @Id
+    private Long id;
+
+    private String username;
+
+    @OneToMany(fetch = FetchType.LAZY)
+    private List<Order> orders;
+
+    // getters and setters
+}
+
+

Compliant solution

+
+@Entity
+public class User {
+    @Id
+    private Long id;
+
+    private String username;
+
+    @OneToMany(fetch = FetchType.LAZY)
+    private List<Order> orders;
+
+    // getters and setters
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.json new file mode 100644 index 00000000000..9d51ebca3fe --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.json @@ -0,0 +1,26 @@ +{ + "title": "JPA entity classes should not be final", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "jpa", + "hibernate", + "pitfall" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-8947", + "sqKey": "S8947", + "scope": "Main", + "quickfix": "unknown", + "code": { + "impacts": { + "RELIABILITY": "HIGH", + "MAINTAINABILITY": "MEDIUM" + }, + "attribute": "LOGICAL" + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json index 43c82fe9248..dc43b833313 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json @@ -537,6 +537,7 @@ "S8909", "S8911", "S8924", + "S8947", "S8948" ] } From b5b7a77a083a6fc0d170cf9930f5d17c96bf9db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 30 Jun 2026 16:40:37 +0200 Subject: [PATCH 2/4] fix autoscan --- .../src/test/resources/autoscan/diffs/diff_S8947.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S8947.json diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8947.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8947.json new file mode 100644 index 00000000000..a320e36e6b6 --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8947.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S8947", + "hasTruePositives": false, + "falseNegatives": 4, + "falsePositives": 0 +} From e82572a4ee4b9b48cedd23c023db3ffe5b2521fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Wed, 1 Jul 2026 15:17:14 +0200 Subject: [PATCH 3/4] add check on final methods --- .../JpaEntityFinalCheckJakartaSample.java | 31 ++++++++++++++++ .../JpaEntityFinalCheckJavaxSample.java | 11 ++++++ .../java/checks/JpaEntityFinalCheck.java | 36 +++++++++++++++---- .../org/sonar/l10n/java/rules/java/S8947.html | 6 ++-- 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java index 5da0d34b986..a573dd72e5c 100644 --- a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java @@ -22,6 +22,32 @@ final class JpaEntityFinalCheckJakartaFinalMappedSuperclass { // Noncompliant {{ private Long id; } +@Entity +class JpaEntityFinalCheckJakartaEntityWithFinalMethod { // Compliant - class itself is not final + @Id + private Long id; + + public final Long getId() { // Noncompliant {{Remove this "final" modifier from this JPA entity method.}} + // ^^^^^ + return id; + } + + public Long getIdCompliant() { // Compliant + return id; + } +} + +@MappedSuperclass +class JpaEntityFinalCheckJakartaMappedSuperclassWithFinalMethod { // Compliant - class itself is not final + @Id + private Long id; + + public final Long getId() { // Noncompliant {{Remove this "final" modifier from this JPA entity method.}} + // ^^^^^ + return id; + } +} + @Entity class JpaEntityFinalCheckJakartaCompliantEntity { // Compliant @Id @@ -43,3 +69,8 @@ class JpaEntityFinalCheckJakartaNotAnEntity { // Compliant - not a JPA entity final class JpaEntityFinalCheckJakartaFinalNotAnEntity { // Compliant - not a JPA entity } + +class JpaEntityFinalCheckJakartaNotAnEntityWithFinalMethod { // Compliant - not a JPA entity + public final void doSomething() { // Compliant - not a JPA entity + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java index c61adc93c15..3d69bfcb478 100644 --- a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java @@ -22,6 +22,17 @@ final class JpaEntityFinalCheckJavaxFinalMappedSuperclass { // Noncompliant {{Re private Long id; } +@Entity +class JpaEntityFinalCheckJavaxEntityWithFinalMethod { // Compliant - class itself is not final + @Id + private Long id; + + public final Long getId() { // Noncompliant {{Remove this "final" modifier from this JPA entity method.}} + // ^^^^^ + return id; + } +} + @Entity class JpaEntityFinalCheckJavaxCompliantEntity { // Compliant @Id diff --git a/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java b/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java index 250561fd8bb..8afc9b6eb49 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java @@ -20,8 +20,10 @@ import org.sonar.check.Rule; import org.sonar.java.model.ModifiersUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.Modifier; import org.sonar.plugins.java.api.tree.ModifierKeywordTree; import org.sonar.plugins.java.api.tree.Tree; @@ -38,20 +40,40 @@ public class JpaEntityFinalCheck extends IssuableSubscriptionVisitor { @Override public List nodesToVisit() { - return List.of(Tree.Kind.CLASS); + return List.of(Tree.Kind.CLASS, Tree.Kind.METHOD); } @Override public void visitNode(Tree tree) { - ClassTree classTree = (ClassTree) tree; - SymbolMetadata metadata = classTree.symbol().metadata(); - if (ENTITY_ANNOTATIONS.stream().noneMatch(metadata::isAnnotatedWith)) { + if (tree.is(Tree.Kind.CLASS)) { + visitClass((ClassTree) tree); + } else { + visitMethod((MethodTree) tree); + } + } + + private void visitClass(ClassTree classTree) { + if (!isJpaEntity(classTree.symbol().metadata())) { return; } + ModifierKeywordTree finalModifier = ModifiersUtils.getModifier(classTree.modifiers(), Modifier.FINAL); + if (finalModifier != null) { + reportIssue(finalModifier, "Remove this \"final\" modifier from this JPA entity class."); + } + } - ModifierKeywordTree finalClassModifier = ModifiersUtils.getModifier(classTree.modifiers(), Modifier.FINAL); - if (finalClassModifier != null) { - reportIssue(finalClassModifier, "Remove this \"final\" modifier from this JPA entity class."); + private void visitMethod(MethodTree methodTree) { + ModifierKeywordTree finalModifier = ModifiersUtils.getModifier(methodTree.modifiers(), Modifier.FINAL); + if (finalModifier == null) { + return; + } + Symbol.TypeSymbol enclosingClass = methodTree.symbol().enclosingClass(); + if (enclosingClass != null && isJpaEntity(enclosingClass.metadata())) { + reportIssue(finalModifier, "Remove this \"final\" modifier from this JPA entity method."); } } + + private static boolean isJpaEntity(SymbolMetadata metadata) { + return ENTITY_ANNOTATIONS.stream().anyMatch(metadata::isAnnotatedWith); + } } diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html index 215896da70f..c494c355a26 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8947.html @@ -67,9 +67,9 @@

Compliant solution

Resources

Documentation

From 4268b8ff703ed67e2d0f79b1f8291bc33251b469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Wed, 1 Jul 2026 17:07:42 +0200 Subject: [PATCH 4/4] fix fp on static/private methods --- .../checks/JpaEntityFinalCheckJakartaSample.java | 12 ++++++++++-- .../java/checks/JpaEntityFinalCheckJavaxSample.java | 10 +++++++++- .../org/sonar/java/checks/JpaEntityFinalCheck.java | 6 +++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java index a573dd72e5c..4150c0b1a0b 100644 --- a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJakartaSample.java @@ -28,13 +28,21 @@ class JpaEntityFinalCheckJakartaEntityWithFinalMethod { // Compliant - class its private Long id; public final Long getId() { // Noncompliant {{Remove this "final" modifier from this JPA entity method.}} - // ^^^^^ + // ^^^^^ return id; } public Long getIdCompliant() { // Compliant return id; } + + private final Long getIdPrivate() { // Compliant - private methods cannot be overridden by proxies + return id; + } + + public static final Long getIdStatic() { // Compliant - static methods cannot be overridden by proxies + return 0L; + } } @MappedSuperclass @@ -43,7 +51,7 @@ class JpaEntityFinalCheckJakartaMappedSuperclassWithFinalMethod { // Compliant - private Long id; public final Long getId() { // Noncompliant {{Remove this "final" modifier from this JPA entity method.}} - // ^^^^^ + // ^^^^^ return id; } } diff --git a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java index 3d69bfcb478..dcdde8692c1 100644 --- a/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/JpaEntityFinalCheckJavaxSample.java @@ -28,9 +28,17 @@ class JpaEntityFinalCheckJavaxEntityWithFinalMethod { // Compliant - class itsel private Long id; public final Long getId() { // Noncompliant {{Remove this "final" modifier from this JPA entity method.}} - // ^^^^^ + // ^^^^^ return id; } + + private final Long getIdPrivate() { // Compliant - private methods cannot be overridden by proxies + return id; + } + + public static final Long getIdStatic() { // Compliant - static methods cannot be overridden by proxies + return 0L; + } } @Entity diff --git a/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java b/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java index 8afc9b6eb49..6897767e35f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/JpaEntityFinalCheck.java @@ -67,7 +67,11 @@ private void visitMethod(MethodTree methodTree) { if (finalModifier == null) { return; } - Symbol.TypeSymbol enclosingClass = methodTree.symbol().enclosingClass(); + Symbol methodSymbol = methodTree.symbol(); + if (methodSymbol.isStatic() || methodSymbol.isPrivate()) { + return; + } + Symbol.TypeSymbol enclosingClass = methodSymbol.enclosingClass(); if (enclosingClass != null && isJpaEntity(enclosingClass.metadata())) { reportIssue(finalModifier, "Remove this \"final\" modifier from this JPA entity method."); }