Skip to content

SONARJAVA-7167 Implement rule S9438 - Private fields that are never reassigned after construction should be declared "final" - #6347

Open
rombirli wants to merge 4 commits into
masterfrom
rombirli/implement-final-private-fields
Open

rombirli wants to merge 4 commits into
masterfrom
rombirli/implement-final-private-fields

Conversation

@rombirli

Copy link
Copy Markdown
Contributor

Implements S9438 for Java.

The rule raises on a private, non-static, non-final instance field that is assigned exactly once during object initialization and never reassigned afterwards, so it can be declared final.

RSPEC: https://github.com/SonarSource/rspec/pull/8340
Jira: https://sonarsource.atlassian.net/browse/SONARJAVA-7167

Scope

A field is reported only when final would actually compile:

  • by its own initializer, with no other assignment, or
  • by a single instance initializer block, or
  • by every constructor that does not delegate to another one, exactly once each.

Not reported

  • compound assignments and increments, which read before writing
  • assignments nested in a conditional, loop, try, lambda or inner class, since they do not always run exactly once
  • assignments through another instance (other.field = ...)
  • fields carrying any annotation, which covers injection, deserialization and mocking frameworks
  • fields of JPA entities, written reflectively outside any constructor
  • classes with a Lombok-generated setter, handled in LombokFilter
  • fields never assigned at all, where final would not compile

A quick fix adds the final modifier.

@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-7167

Comment thread java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java
@datadog-sonarsource

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6348

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Ruling Diff Summary

Detected changes in 7 rule files: 0 issues removed, 307 issues added.

S9438 (java) on commons-beanutils - 0 issues removed, 3 issues added - new ruling file

Added src/main/java/org/apache/commons/beanutils2/BeanMap.java (line 51)

        46 |  */
        47 | public class BeanMap extends AbstractMap<Object, Object> implements Cloneable {
        48 | 
        49 |     private transient Object bean;
        50 | 
>>>     51 |     private transient HashMap<String, Method> readMethods = new HashMap<>();
        52 |     private transient HashMap<String, Method> writeMethods = new HashMap<>();
        53 |     private transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
        54 | 
        55 |     /**
        56 |      * An empty array.  Used to invoke accessors via reflection.

Added src/main/java/org/apache/commons/beanutils2/BeanMap.java (line 52)

        47 | public class BeanMap extends AbstractMap<Object, Object> implements Cloneable {
        48 | 
        49 |     private transient Object bean;
        50 | 
        51 |     private transient HashMap<String, Method> readMethods = new HashMap<>();
>>>     52 |     private transient HashMap<String, Method> writeMethods = new HashMap<>();
        53 |     private transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
        54 | 
        55 |     /**
        56 |      * An empty array.  Used to invoke accessors via reflection.
        57 |      */

Added src/main/java/org/apache/commons/beanutils2/BeanMap.java (line 53)

        48 | 
        49 |     private transient Object bean;
        50 | 
        51 |     private transient HashMap<String, Method> readMethods = new HashMap<>();
        52 |     private transient HashMap<String, Method> writeMethods = new HashMap<>();
>>>     53 |     private transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
        54 | 
        55 |     /**
        56 |      * An empty array.  Used to invoke accessors via reflection.
        57 |      */
        58 |     public static final Object[] NULL_ARGUMENTS = {};
S9438 (java) on eclipse-jetty - 0 issues removed, 34 issues added - new ruling file

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java (line 60)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java (line 171)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java)

Added jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java (line 124)

(source file not found at this revision: jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java (line 59)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java (line 496)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java (line 82)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java (line 46)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java (line 35)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java (line 335)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java (line 36)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java (line 38)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java (line 39)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java (line 1067)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java (line 30)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java (line 31)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java)
S9438 (java) on eclipse-jetty-similar-to-main - 0 issues removed, 17 issues added - new ruling file

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java (line 60)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java (line 171)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java)

Added jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java (line 124)

(source file not found at this revision: jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java (line 59)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java (line 496)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java (line 82)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java (line 46)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java (line 35)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java (line 335)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java (line 36)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java (line 38)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java (line 39)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java (line 1067)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java (line 30)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java (line 31)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/session/UnreadableSessionDataException.java)
S9438 (java) on guava - 0 issues removed, 6 issues added - new ruling file

Added src/com/google/common/base/MoreObjects.java (line 139)

       134 |    * @author Jason Lee
       135 |    * @since 18.0 (since 2.0 as {@code Objects.ToStringHelper}).
       136 |    */
       137 |   public static final class ToStringHelper {
       138 |     private final String className;
>>>    139 |     private ValueHolder holderHead = new ValueHolder();
       140 |     private ValueHolder holderTail = holderHead;
       141 |     private boolean omitNullValues = false;
       142 | 
       143 |     /**
       144 |      * Use {@link MoreObjects#toStringHelper(Object)} to create an instance.

Added src/com/google/common/base/Objects.java (line 205)

       200 |    *      scheduled for removal in June 2016.
       201 |    */
       202 |   @Deprecated
       203 |   public static final class ToStringHelper {
       204 |     private final String className;
>>>    205 |     private ValueHolder holderHead = new ValueHolder();
       206 |     private ValueHolder holderTail = holderHead;
       207 |     private boolean omitNullValues = false;
       208 | 
       209 |     /**
       210 |      * Use {@link Objects#toStringHelper(Object)} to create an instance.

Added src/com/google/common/collect/ConcurrentHashMultiset.java (line 511)

       506 |   Iterator<Entry<E>> entryIterator() {
       507 |     // AbstractIterator makes this fairly clean, but it doesn't support remove(). To support
       508 |     // remove(), we create an AbstractIterator, and then use ForwardingIterator to delegate to it.
       509 |     final Iterator<Entry<E>> readOnlyIterator =
       510 |         new AbstractIterator<Entry<E>>() {
>>>    511 |           private Iterator<Map.Entry<E, AtomicInteger>> mapEntries = countMap.entrySet().iterator();
       512 | 
       513 |           @Override
       514 |           protected Entry<E> computeNext() {
       515 |             while (true) {
       516 |               if (!mapEntries.hasNext()) {

Added src/com/google/common/collect/ImmutableSortedMap.java (line 507)

       502 |     }
       503 |   }
       504 | 
       505 |   private final transient RegularImmutableSortedSet<K> keySet;
       506 |   private final transient ImmutableList<V> valueList;
>>>    507 |   private transient ImmutableSortedMap<K, V> descendingMap;
       508 | 
       509 |   ImmutableSortedMap(RegularImmutableSortedSet<K> keySet, ImmutableList<V> valueList) {
       510 |     this(keySet, valueList, null);
       511 |   }
       512 | 

Added src/com/google/common/io/MultiInputStream.java (line 36)

        31 |  * @author Chris Nokleberg
        32 |  * @since 1.0
        33 |  */
        34 | final class MultiInputStream extends InputStream {
        35 | 
>>>     36 |   private Iterator<? extends ByteSource> it;
        37 |   private InputStream in;
        38 | 
        39 |   /**
        40 |    * Creates a new instance.
        41 |    *

Added src/com/google/common/util/concurrent/SmoothRateLimiter.java (line 210)

       205 |      * The slope of the line from the stable interval (when permits == 0), to the cold interval
       206 |      * (when permits == maxPermits)
       207 |      */
       208 |     private double slope;
       209 |     private double thresholdPermits;
>>>    210 |     private double coldFactor;
       211 | 
       212 |     SmoothWarmingUp(
       213 |         SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit, double coldFactor) {
       214 |       super(stopwatch);
       215 |       this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod);
S9438 (java) on jboss-ejb3-tutorial - 0 issues removed, 7 issues added - new ruling file

Added cachedentity/src/org/jboss/tutorial/cachedentity/bean/EntityTestBean.java (line 50)

        45 |    private EntityManager manager;
        46 |    
        47 |    /**
        48 |     * Logger
        49 |     */
>>>     50 |    private Logger logger = Logger.getLogger(EntityTestBean.class);
        51 | 
        52 |    public Customer createCustomer()
        53 |    {
        54 |       Customer customer = new Customer();
        55 |       customer.setName("JBoss");

Added injection/src/org/jboss/tutorial/injection/bean/ShoppingCartBean.java (line 35)

        30 | 
        31 | @Stateful
        32 | @Remote(ShoppingCart.class)
        33 | public class ShoppingCartBean implements ShoppingCart, java.io.Serializable
        34 | {
>>>     35 |    private HashMap<String, Integer> cart = new HashMap<String, Integer>();
        36 | 
        37 |    @EJB
        38 |    private Calculator calculator;
        39 | 
        40 | 

Added jboss_deployment_descriptor/src/org/jboss/tutorial/jboss_deployment_descriptor/bean/ShoppingCartBean.java (line 29)

        24 | import java.io.Serializable;
        25 | import java.util.HashMap;
        26 | 
        27 | public class ShoppingCartBean implements ShoppingCart, Serializable
        28 | {
>>>     29 |    private HashMap<String, Integer> cart = new HashMap<String, Integer>();
        30 | 
        31 |    public void buy(String product, int quantity)
        32 |    {
        33 |       if (cart.containsKey(product))
        34 |       {

Added jndibinding/src/org/jboss/tutorial/jndibinding/bean/CustomerBean.java (line 46)

        41 | @Remote(CustomerManager.class)
        42 | @RemoteBinding(jndiBinding = "CustomerManager")
        43 | public class CustomerBean implements CustomerManager
        44 | {
        45 |    // Logger
>>>     46 |    private Logger logger = Logger.getLogger(CustomerBean.class);
        47 | 
        48 |    // must match the one in persistence.xml
        49 |    private static final String ENTITY_MANAGER_JNDI_NAME = "java:/jboss-ejb3-tutorial-jndibinding-EntityManager";
        50 |    
        51 |    public long createCustomer(String name)

Added partial_deployment_descriptor/src/org/jboss/tutorial/partial_deployment_descriptor/bean/ExternalCallbackListener.java (line 40)

        35 | {
        36 | 
        37 |    /**
        38 |     * Logger
        39 |     */
>>>     40 |    private Logger logger = Logger.getLogger(ExternalCallbackListener.class);
        41 |    
        42 |    public Object postConstruct(InvocationContext invocationContext) throws Exception
        43 |    {
        44 |       logger.info("Post-construct invoked for bean " + invocationContext.getTarget());
        45 |       return invocationContext.proceed();

Added stateful/src/org/jboss/tutorial/stateful/bean/ShoppingCartBean.java (line 35)

        30 | 
        31 | @Stateful
        32 | @Remote(ShoppingCart.class)
        33 | public class ShoppingCartBean implements ShoppingCart, Serializable
        34 | {
>>>     35 |    private HashMap<String, Integer> cart = new HashMap<String, Integer>();
        36 | 
        37 |    public void buy(String product, int quantity)
        38 |    {
        39 |       if (cart.containsKey(product))
        40 |       {

Added stateful_deployment_descriptor/src/org/jboss/tutorial/stateful_deployment_descriptor/bean/ShoppingCartBean.java (line 29)

        24 | import java.io.Serializable;
        25 | import java.util.HashMap;
        26 | 
        27 | public class ShoppingCartBean implements ShoppingCart, Serializable
        28 | {
>>>     29 |    private HashMap<String, Integer> cart = new HashMap<String, Integer>();
        30 | 
        31 |    public void buy(String product, int quantity)
        32 |    {
        33 |       if (cart.containsKey(product))
        34 |       {
S9438 (java) on mall - 0 issues removed, 155 issues added - new ruling file

Added mall-common/src/main/java/com/macro/mall/common/api/ResultCode.java (line 13)

         8 |     SUCCESS(200, "操作成功"),
         9 |     FAILED(500, "操作失败"),
        10 |     VALIDATE_FAILED(404, "参数检验失败"),
        11 |     UNAUTHORIZED(401, "暂未登录或token已经过期"),
        12 |     FORBIDDEN(403, "没有相关权限");
>>>     13 |     private long code;
        14 |     private String message;
        15 | 
        16 |     private ResultCode(long code, String message) {
        17 |         this.code = code;
        18 |         this.message = message;

Added mall-common/src/main/java/com/macro/mall/common/api/ResultCode.java (line 14)

         9 |     FAILED(500, "操作失败"),
        10 |     VALIDATE_FAILED(404, "参数检验失败"),
        11 |     UNAUTHORIZED(401, "暂未登录或token已经过期"),
        12 |     FORBIDDEN(403, "没有相关权限");
        13 |     private long code;
>>>     14 |     private String message;
        15 | 
        16 |     private ResultCode(long code, String message) {
        17 |         this.code = code;
        18 |         this.message = message;
        19 |     }

Added mall-demo/src/main/java/com/macro/mall/demo/bo/AdminUserDetails.java (line 16)

        11 | /**
        12 |  * SpringSecurity需要的用户详情
        13 |  * Created by macro on 2018/4/26.
        14 |  */
        15 | public class AdminUserDetails implements UserDetails {
>>>     16 |     private UmsAdmin umsAdmin;
        17 | 
        18 |     public AdminUserDetails(UmsAdmin umsAdmin) {
        19 |         this.umsAdmin = umsAdmin;
        20 |     }
        21 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsHelpCategoryExample.java (line 495)

       490 |             super();
       491 |         }
       492 |     }
       493 | 
       494 |     public static class Criterion {
>>>    495 |         private String condition;
       496 | 
       497 |         private Object value;
       498 | 
       499 |         private Object secondValue;
       500 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsHelpCategoryExample.java (line 509)

       504 | 
       505 |         private boolean betweenValue;
       506 | 
       507 |         private boolean listValue;
       508 | 
>>>    509 |         private String typeHandler;
       510 | 
       511 |         public String getCondition() {
       512 |             return condition;
       513 |         }
       514 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsHelpExample.java (line 556)

       551 |             super();
       552 |         }
       553 |     }
       554 | 
       555 |     public static class Criterion {
>>>    556 |         private String condition;
       557 | 
       558 |         private Object value;
       559 | 
       560 |         private Object secondValue;
       561 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsHelpExample.java (line 570)

       565 | 
       566 |         private boolean betweenValue;
       567 | 
       568 |         private boolean listValue;
       569 | 
>>>    570 |         private String typeHandler;
       571 | 
       572 |         public String getCondition() {
       573 |             return condition;
       574 |         }
       575 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsMemberReportExample.java (line 626)

       621 |             super();
       622 |         }
       623 |     }
       624 | 
       625 |     public static class Criterion {
>>>    626 |         private String condition;
       627 | 
       628 |         private Object value;
       629 | 
       630 |         private Object secondValue;
       631 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsMemberReportExample.java (line 640)

       635 | 
       636 |         private boolean betweenValue;
       637 | 
       638 |         private boolean listValue;
       639 | 
>>>    640 |         private String typeHandler;
       641 | 
       642 |         public String getCondition() {
       643 |             return condition;
       644 |         }
       645 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsPrefrenceAreaExample.java (line 435)

       430 |             super();
       431 |         }
       432 |     }
       433 | 
       434 |     public static class Criterion {
>>>    435 |         private String condition;
       436 | 
       437 |         private Object value;
       438 | 
       439 |         private Object secondValue;
       440 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsPrefrenceAreaExample.java (line 449)

       444 | 
       445 |         private boolean betweenValue;
       446 | 
       447 |         private boolean listValue;
       448 | 
>>>    449 |         private String typeHandler;
       450 | 
       451 |         public String getCondition() {
       452 |             return condition;
       453 |         }
       454 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsPrefrenceAreaProductRelationExample.java (line 295)

       290 |             super();
       291 |         }
       292 |     }
       293 | 
       294 |     public static class Criterion {
>>>    295 |         private String condition;
       296 | 
       297 |         private Object value;
       298 | 
       299 |         private Object secondValue;
       300 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsPrefrenceAreaProductRelationExample.java (line 309)

       304 | 
       305 |         private boolean betweenValue;
       306 | 
       307 |         private boolean listValue;
       308 | 
>>>    309 |         private String typeHandler;
       310 | 
       311 |         public String getCondition() {
       312 |             return condition;
       313 |         }
       314 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsSubjectCategoryExample.java (line 495)

       490 |             super();
       491 |         }
       492 |     }
       493 | 
       494 |     public static class Criterion {
>>>    495 |         private String condition;
       496 | 
       497 |         private Object value;
       498 | 
       499 |         private Object secondValue;
       500 | 

Added mall-mbg/src/main/java/com/macro/mall/model/CmsSubjectCategoryExample.java (line 509)

       504 | 
       505 |         private boolean betweenValue;
       506 | 
       507 |         private boolean listValue;
       508 | 
>>>    509 |         private String typeHandler;
       510 | 
       511 |         public String getCondition() {
       512 |             return condition;
       513 |         }
       514 | 
S9438 (java) on sonarqube-6.5 - 0 issues removed, 85 issues added - new ruling file

Added src/main/java/org/sonar/ce/queue/CeQueueImpl.java (line 56)

(source file not found at this revision: src/main/java/org/sonar/ce/queue/CeQueueImpl.java)

Added src/main/java/org/sonar/server/app/TomcatAccessLog.java (line 62)

(source file not found at this revision: src/main/java/org/sonar/server/app/TomcatAccessLog.java)

Added src/main/java/org/sonar/server/authentication/SsoAuthenticator.java (line 90)

(source file not found at this revision: src/main/java/org/sonar/server/authentication/SsoAuthenticator.java)

Added src/main/java/org/sonar/server/batch/ProjectDataLoader.java (line 218)

(source file not found at this revision: src/main/java/org/sonar/server/batch/ProjectDataLoader.java)

Added src/main/java/org/sonar/server/batch/ProjectDataLoader.java (line 219)

(source file not found at this revision: src/main/java/org/sonar/server/batch/ProjectDataLoader.java)

Added src/main/java/org/sonar/server/batch/ProjectDataLoader.java (line 220)

(source file not found at this revision: src/main/java/org/sonar/server/batch/ProjectDataLoader.java)

Added src/main/java/org/sonar/server/component/ws/SuggestionsAction.java (line 93)

(source file not found at this revision: src/main/java/org/sonar/server/component/ws/SuggestionsAction.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Analysis.java (line 31)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Analysis.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Analysis.java (line 32)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Analysis.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Analysis.java (line 33)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Analysis.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/duplication/DuplicationRepositoryImpl.java (line 36)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/duplication/DuplicationRepositoryImpl.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/duplication/IntegrateCrossProjectDuplications.java (line 58)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/duplication/IntegrateCrossProjectDuplications.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortAggregator.java (line 62)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortAggregator.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortAggregator.java (line 63)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortAggregator.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/issue/filter/IssuePattern.java (line 30)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/issue/filter/IssuePattern.java)

@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6348

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6348

@rombirli
rombirli force-pushed the rombirli/implement-final-private-fields branch from 0982ece to e5f6e3c Compare October 14, 2026 11:53
Comment thread java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java Outdated
@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6348

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6348

@rombirli
rombirli force-pushed the rombirli/implement-final-private-fields branch 4 times, most recently from 272e824 to 952391e Compare October 14, 2026 13:42
@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6351

@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6351

@rombirli
rombirli force-pushed the rombirli/implement-final-private-fields branch 2 times, most recently from bc39f50 to e887a58 Compare October 14, 2026 14:57
@rombirli
rombirli force-pushed the rombirli/implement-final-private-fields branch from e887a58 to 9b9da57 Compare October 14, 2026 15:24
@rombirli
rombirli force-pushed the rombirli/implement-final-private-fields branch from 9b9da57 to 3ba0950 Compare October 14, 2026 15:50
@rombirli
rombirli force-pushed the rombirli/implement-final-private-fields branch from 3ba0950 to f886d0e Compare October 14, 2026 16:15
@sonarqube-next

Copy link
Copy Markdown
Contributor

@gitar-bot

gitar-bot Bot commented Oct 14, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 13 closed / 13 findings

🟡 Medium risk · Adding S9438 analysis and a quick fix that marks fields final could misclassify initialization writes, producing compile-breaking edits.

Adds rule S9438 to flag private instance fields that are assigned once during initialization and never reassigned, so they can be declared final, with a quick fix that adds the modifier. Several false positives and a multi-variable quick-fix bug were resolved this session. No open issues remain; several edge cases are closed without a fix in this PR, including unary reads, reads before the constructor assignment, and Lombok-annotated classes that already make fields final.

✅ 13 closed
✅ Bug: Writes in delegating constructors are ignored, so the rule raises false positives

📄 java-checks/src/main/java/org/sonar/java/checks/PrivateFieldShouldBeFinalCheck.java:129-131 📄 java-checks/src/main/java/org/sonar/java/checks/PrivateFieldShouldBeFinalCheck.java:301-315
collectWrites records a direct assignment inside a constructor that starts with this(...) in assigningConstructors. makesFinalPossible then filters those constructors out of required and never looks at them again. Take A(String v) { this.x = v; } and A() { this("d"); this.x = "y"; }: every required constructor assigns x exactly once, so the issue is raised. With final added, x is assigned twice on the A() path and the code no longer compiles ("variable x might already have been assigned"). The fix is to reject the field when any delegating constructor assigns it.

✅ Bug: Initializer blocks of nested or anonymous classes count as the field's own

📄 java-checks/src/main/java/org/sonar/java/checks/PrivateFieldShouldBeFinalCheck.java:132-133 📄 java-checks/src/main/java/org/sonar/java/checks/PrivateFieldShouldBeFinalCheck.java:219-222
isDirectlyInInstanceInitializer only checks that the enclosing block has kind INITIALIZER. It never checks that the block belongs to the class declaring the field. Field usages include writes from nested, local and anonymous classes. So an instance initializer such as new Object() {{ outerField = 1; }}, or the initializer of an inner class, is counted as the one initializer-block write, and the field is reported. A final field cannot be assigned from another class's initializer, so applying the fix breaks compilation. The fix is to require block.parent() to be the ClassTree currently being visited.

✅ Bug: Quick fix makes every variable in a multi-variable declaration final

📄 java-checks/src/main/java/org/sonar/java/checks/PrivateFieldShouldBeFinalCheck.java:94-96
In JParser.processFieldDeclaration, every fragment of private int a, b; shares the same ModifiersTree and TypeTree. The quick fix inserts final before field.type(), so fixing a turns the declaration into private final int a, b;. If b is reassigned or never assigned, the quick fix leaves code that does not compile. Either skip the quick fix when the type is shared with another VariableTree, or split the declaration first.

✅ Edge Case: An early return in a constructor before the assignment is not detected

📄 java-checks/src/main/java/org/sonar/java/checks/PrivateFieldShouldBeFinalCheck.java:202-216
enclosingConstructor accepts any assignment written directly in the constructor body and does not check what runs before it. For A(int v) { if (v < 0) { return; } this.x = v; } the field counts as assigned exactly once and is reported. With final added, x is not definitely assigned on the return path, so the code fails to compile. The rule's description promises it only reports when final would compile. A fix is to disqualify the field when a constructor body contains a return statement (outside nested lambdas and classes) before the assignment.

✅ Edge Case: Lombok @NoArgsConstructor classes are reported, but final breaks compilation

📄 java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java:110-114 📄 java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java:140
The new Lombok exclusion only covers annotations that generate setters (@Setter, @Data, @Builder, @SuperBuilder). Take a class annotated with @NoArgsConstructor that also has A(String x) { this.x = x; }: it is reported, because the generated no-arg constructor is not in the AST. Lombok rejects @NoArgsConstructor when there are uninitialized final fields unless force = true, so applying the fix breaks the build. @RequiredArgsConstructor causes a similar problem, since making the field final changes the generated constructor and can clash with the hand-written one. The fix is to also exclude PrivateFieldShouldBeFinalCheck for classes with Lombok constructor-generating annotations, for example the existing GENERATE_CONSTRUCTOR list.

...and 8 more closed from earlier reviews

Verification Knowledge ✅ 4 · 🔎 1 PRs 1 inspected

Functional validation No results

Rules No rules evaluated

Knowledge ✅ 4 expectations met 🔎 1 informed a finding

Expectation met Delegating-constructor assignments block final-field findings expectation met
🔎 Initializer blocks must belong to the declaring class informed a finding, won't fix -> Reads in nested/anonymous class initializers count as reads before init
Expectation met Other-instance reads do not block final-field detection expectation met
Expectation met Quick fixes are omitted for multi-variable declarations expectation met
Expectation met Right-hand-side reads count before initialization expectation met

Broader impact PRs 1 inspected
Cross-PR 1 PR inspected 0 open impacts

PR #6308: SONARJAVA-7137 Optionally rely on source paths to resolve types during analyses Inspected

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

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

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

@rombirli

Copy link
Copy Markdown
Contributor Author

/pvf

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 5666 new · 🗑️ 0 lost issues
View dashboard

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 188 new · 🗑️ 0 lost issues
View dashboard


private String assignedInConstructor; // Noncompliant {{Make this private field "final", it is never reassigned after construction.}} [[secondary=39]]
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
private List<String> assignedAtDeclaration = new ArrayList<>(); // Noncompliant

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you put the issue message here as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The issue message is not dynamically constructed, it is a constant one. I dont see the point of testing it multiple times.

@rombirli rombirli Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

!CHANGE ISSUE MESSAGE (NOT USE CONSTRUCTION WORD)

Comment on lines +16 to +17
<p>The rule raises an issue on a <code>private</code>, non-<code>final</code> instance field whose value is assigned only during object initialization
and never reassigned afterwards.</p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this should be the first sentence in the rule description

Comment on lines +36 to +37
<li>Fields that are not assigned on every constructor path, since a <code>final</code> field must be definitely assigned by the end of each
constructor and the code would no longer compile.</li>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we really check that in the implementation? That would require using the CFG

private static Tree enclosingWriteExpression(IdentifierTree usage) {
Tree target = usage;
Tree parent = usage.parent();
if (parent != null && parent.is(Tree.Kind.MEMBER_SELECT) && ((MemberSelectExpressionTree) parent).identifier() == usage) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need to unwrap parentheses around the expression, looking just at the parent is not enough.

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 5666 new · 🗑️ 0 lost issues
View dashboard

This branch has not been deployed

No deployments
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