diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html index 7b01700531a..c5fdcf0e340 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html @@ -39,7 +39,7 @@
This rule also examines the credential value itself: a value that is recognizable as something other than a real credential is not reported, even +when the variable name or the connection string parameter matches one of the configured words. Placeholders, references to an environment variable or +to a secrets manager, encrypted values, and obvious example or test values are therefore not flagged.
Credentials should be stored in a configuration file that is not committed to the code repository, in a database, or managed by your cloud provider’s secrets management service. If a password is exposed in the source code, it must be changed immediately.
@@ -15,7 +18,7 @@
String username = "steve";
-String password = "blue";
+String password = "Xk9mP2vL8qR4";
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test?" +
"user=" + username + "&password=" + password); // Noncompliant
@@ -30,7 +33,7 @@ This rule is deprecated; use {rule:java:S6813} instead.
Spring @Controller, @Service, and @Repository classes are singletons by default, meaning only one instance
of the class is ever instantiated in the application. Typically such a class might have a few static members, such as a logger, but all
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4288.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4288.json
index c70a6818f60..ee419381154 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4288.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4288.json
@@ -7,16 +7,13 @@
},
"attribute": "CONVENTIONAL"
},
- "status": "ready",
+ "status": "deprecated",
"remediation": {
"func": "Linear",
"linearDesc": "2min by injected field",
"linearFactor": "2min"
},
- "tags": [
- "spring",
- "design"
- ],
+ "tags": [],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-4288",
"sqKey": "S4288",
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4347.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4347.html
index 0cfd315392c..ca4954a4539 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4347.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S4347.html
@@ -64,7 +64,7 @@
AssertJ contains many assertions methods specific to common types. Both versions will test the same things, but the dedicated one will provide a -better error message, simplifying the debugging process.
-This rule reports an issue when an assertion can be simplified to a dedicated one.
-The array below gives a non-exhaustive list of assertion reported by the rule. Code behaving similarly, or with a negation will also be -reported.
+A dedicated AssertJ assertion receives the value under test, so its failure message can show that value. For example, when
+assertThat(names).contains("Ada") fails, it shows the collection and the missing element.
+assertThat(names.contains("Ada")).isTrue() only reports that false was not true.
+assertThat(names.contains("Ada")).isTrue(); // Noncompliant
+
+
+assertThat(names).contains("Ada");
+
+The following catalog is not exhaustive. Similar assertions and negated forms may also be reported. Choose a matcher that expresses the intended +test, and check the AssertJ version used by the project.
+The object-identity mapping for x == y applies to reference values. Check null and edge-case behavior when replacing a derived
+expression: for example, map.get(key) can return null for a missing key, whereas containsEntry(key, null)
+requires the key to exist. For floating-point values, check NaN and signed-zero behavior rather than assuming a relational-expression rewrite is
+equivalent.
|
-
|
-
-
|
-
|
Why is this an issue? | |
|
-
|
Why is this an issue? |
|
-
|
-
|
|
-
|
Why is this an issue? |
|
-
|
-
|
-assertThat(getObject()).isEqualTo(null); // Noncompliant -assertThat(getObject()).isNotEqualTo(null); // Noncompliant - not listed above but also supported - -assertThat(getString().trim()).isEmpty(); -assertThat(getFile().canRead()).isTrue(); -assertThat(getPath().getParent()).isNull(); --
-assertThat(getObject()).isNull(); -assertThat(getObject()).isNotNull(); - -assertThat(getString()).isBlank(); -assertThat(getFile()).canRead(); -assertThat(getPath()).hasNoParentRaw(); -diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5876.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5876.html index bc4ecf48b1f..3319bec3777 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5876.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5876.html @@ -78,7 +78,7 @@
A method annotated with Spring’s @Async, @Cacheable or @Transactional annotations will not work as expected
-if invoked directly from within its class.
This is because Spring generates a proxy class with wrapper code to manage the method’s asynchronicity (@Async), to cache methods
-invocations (@Cacheable), or to handle the transaction (@Transactional). However, when called using this, the
-proxy instance is bypassed, and the method is invoked directly without the required wrapper code.
With Spring’s proxy-based configuration, calling an @Async, @Cacheable, or @Transactional method from its
+own class bypasses the proxy that applies the annotation’s behavior. For example, an @Async method runs on the calling thread instead of
+being scheduled asynchronously. Move the annotated operation to a separate Spring-managed bean and call it through an injected reference.
Replace calls to @Async, @Cacheable or @Transactional methods via this with calls on an
-instance that was injected by Spring (@Autowired, @Resource or @Inject). The injected instance is a proxy on
-which the methods can be invoked safely.
The example assumes asynchronous method execution is enabled with @EnableAsync and both services are discovered by component scanning.
+Constructor injection gives the calling service a reference to the managed bean, allowing Spring’s proxy to intercept the call.
@Service
-public class AsyncNotificationProcessor implements NotificationProcessor {
-
- @Override
- public void process(Notification notification) {
- processAsync(notification); // Noncompliant, call bypasses proxy
- retrieveNotification(notification.id); // Noncompliant, call bypasses proxy and will not be cached
+public class NotificationProcessor {
+ public void process(String message) {
+ sendAsync(message); // Noncompliant: call bypasses the proxy
}
@Async
- public processAsync(Notification notification) {
- // ...
- }
-
- @Cacheable
- public Notification retrieveNotification(Long id) {
- // ...
+ public void sendAsync(String message) {
+ System.out.println(message);
}
-
}
Put these services in separate source files.
@Service
-public class AsyncNotificationProcessor implements NotificationProcessor {
-
- @Resource
- private AsyncNotificationProcessor asyncNotificationProcessor;
+public class NotificationProcessor {
+ private final NotificationSender sender;
- @Override
- public void process(Notification notification) {
- asyncNotificationProcessor.processAsync(notification); // Compliant, call via injected proxy
- asyncNotificationProcessor.retrieveNotification(notification.id); // Compliant, the call will be cached
+ public NotificationProcessor(NotificationSender sender) {
+ this.sender = sender;
}
- @Async
- public processAsync(Notification notification) {
- // ...
+ public void process(String message) {
+ sender.sendAsync(message);
}
+}
- @Cacheable
- public Notification retrieveNotification(Long id) {
- // ...
+@Service
+public class NotificationSender {
+ @Async
+ public void sendAsync(String message) {
+ System.out.println(message);
}
}
+The annotations are org.springframework.stereotype.Service, org.springframework.scheduling.annotation.Async, and
+org.springframework.scheduling.annotation.EnableAsync.
The same call boundary matters for caching and transactions:
+@Cacheable: self-invocation bypasses the cache lookup and population that would wrap the call.@Transactional: self-invocation does not apply the called method’s transaction settings. The method may still run in a transaction
+ already started by its caller.Enable the relevant Spring feature and use a method that the configured proxy can intercept. Creating the collaborator with new
+bypasses Spring management and does not fix the problem. This explanation concerns proxy mode; AspectJ weaving has different self-invocation
+behavior.
Avoid using self-injection as the default solution. It introduces a circular dependency and depends on configuration that may reject circular +references. Separating the operation into a collaborating bean makes the intercepted call explicit.
This rule is deprecated; use {rule:java:S9352} instead.
Naming conventions play a crucial role in maintaining code clarity and readability. The uniqueness of bean names in Spring configurations is vital to the clarity and readability of the code. When two beans share the same name within a configuration, it is not obvious to the reader which bean is diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.json index a6e9129323f..01461fc1ab2 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.json @@ -1,14 +1,12 @@ { "title": "Beans in \"@Configuration\" class should have different names", "type": "BUG", - "status": "ready", + "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, - "tags": [ - "spring" - ], + "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6862", "sqKey": "S6862", diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7158.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7158.html index cc3db143ea2..4d8900d7075 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7158.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7158.html @@ -2,6 +2,14 @@
Calling String.isEmpty() clearly communicates the code’s intention, which is to test if the string is empty. Using
String.length() == 0 is less direct and makes the code less readable. This preference for isEmpty() extends to all
CharSequence objects, including StringBuilder and StringBuffer.
A String.strip().length() == 0 or String.trim().length() == 0 test is a blankness check, and
+String.isBlank() states that intent directly.
It is also cheaper. Invoking operations that create new string objects with white space removed incurs unnecessary memory allocation and management
+overhead: whenever there is white space to remove, strip() and trim() copy the remaining characters into a new string, which
+is then discarded as soon as its length has been read. isBlank() inspects the characters in place and allocates nothing.
String.isBlank() is only available from Java 11 on. This rule does not raise an issue on strip().length() or
+trim().length() comparisons when the project targets an earlier version.
+if ("string".strip().length() == 0) { /* … */ } // Noncompliant
+
+if ("string".trim().length() > 0) { /* … */ } // Noncompliant
+
if ("string".isEmpty()){ /* … */ }
@@ -26,10 +39,27 @@ Compliant solution
...
if (sb.isEmpty()) { /* … */ }
+
+if ("string".isBlank()) { /* … */ }
+
+if (!"string".isBlank()) { /* … */ }
+
+strip() removes exactly the characters isBlank() treats as white space, both relying on
+Character.isWhitespace, so strip().length() == 0 and isBlank() always agree.
trim() does not. It removes every character up to and including U+0020, which covers control characters such as
+U+0000 that Character.isWhitespace rejects, and it leaves Unicode white space above U+0020, such as the em
+space U+2003, in place. Replacing trim().length() == 0 with isBlank() therefore makes strings of Unicode white
+space count as blank and strings of control characters count as non-blank. The issue message and the quick fix say so explicitly; apply the fix only
+when that is the intended behavior, and keep trim().isEmpty() otherwise.
Spring resolves autowiring by type: when exactly one bean matches the required type, it gets injected without further input. If two or more beans
-of the same type exist in the application context, Spring cannot decide which one to use and expects the ambiguity to be resolved explicitly, either
-with @Qualifier or by marking one bean @Primary.
Without this disambiguation, Spring throws a NoUniqueBeanDefinitionException while building the application context.
@Qualifier, by marking one bean @Primary, or by ensuring only one candidate is registered.
The application will fail to start with an error indicating ambiguous dependency resolution during the dependency injection framework’s -initialization phase. This prevents the application from running and requires immediate attention to resolve the configuration error.
+Spring will throw a NoUniqueBeanDefinitionException exception at runtime, indicating ambiguous dependency resolution during the
+dependency injection framework’s initialization phase. This prevents the application from running and requires immediate attention to resolve the
+configuration error.
The ambiguity can be resolved in several ways, for example with @Qualifier, @Primary, or by restricting which beans are
+registered with @Conditional and its variants. The two most common options are shown below.
Option 1: select the bean with @Qualifier
Use the @Qualifier annotation at the injection point to explicitly specify which bean to inject. The qualifier value must match the
bean name (typically the method name for @Bean methods).
This approach is useful when different parts of your application need different beans of the same type. Each injection point can specify exactly which bean it requires.
-@Configuration @@ -57,6 +60,7 @@+Compliant solution
private DataSource dataSource; // Compliant; @Qualifier explicitly specifies that primaryDataSource needs to be used }
Option 2: mark a default bean with @Primary
Use the @Primary annotation on one of the bean definitions to mark it as the default choice. When Spring encounters multiple beans of
the same type, it will automatically select the primary bean if no explicit qualifier is specified.
This approach is useful when you have a clear default bean that should be used in most cases, with alternative beans available for specific @@ -119,5 +123,10 @@
Many APIs provide built-in formatting capabilities. When you wrap generic string formatting functions around these APIs, you’re adding an unnecessary layer that hurts both performance and readability.
-Generic string formatting functions are typically eagerly evaluated. This means the formatted string is created immediately, even if it’s never used. This is particularly problematic in logging scenarios:
@@ -21,7 +21,7 @@Performance concerns
LOG.debug("Processing {} items", items.size()); // Only formats if DEBUG is enabled-Readability and idioms
+Readability and idioms
Each API has its own idiomatic way of formatting. Using generic string formatting makes the code less recognizable to developers familiar with these frameworks:
In Java, this specifically refers to redundant String.format calls.
SLF4J and similar logging frameworks (Log4j2, Logback) support parameterized logging with {} placeholders. This approach defers string
-construction until the log level is enabled.
Arguments implementing java.util.Formattable are an exception: String.format formats them with
-formatTo instead of toString, so replacing it with {} placeholders could change the message.
Pass the format string and its arguments directly to the API instead of pre-formatting them with String.format. How to do this depends
+on the API:
{} placeholders. This
+ approach defers string construction until the log level is enabled. Arguments implementing java.util.Formattable are an exception:
+ String.format formats them with formatTo instead of toString, so replacing it with {}
+ placeholders could change the message.System.out/System.err): use printf instead of println with
+ String.format. The printf method supports the same format specifiers as String.format.PrintStream and PrintWriter both provide printf and format methods. Use
+ these instead of wrapping String.format in print or println.Note that println appends a line separator, so use %n at the end of the format string when switching to
+printf.
In the examples below, activateUser illustrates the SLF4J case, displayResult the console output case, and
+writeHeader the PrintWriter case.
+import java.io.PrintWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-class UserService {
- private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
+class Example {
+ private static final Logger LOG = LoggerFactory.getLogger(Example.class);
void activateUser(String username, String role) {
LOG.info(String.format("Activated user '%s' with role '%s'", username, role)); // Noncompliant
}
+
+ void displayResult(int a, int b, int sum) {
+ System.out.println(String.format("Result: %d + %d = %d", a, b, sum)); // Noncompliant
+ }
+
+ void writeHeader(PrintWriter writer, String title, int pageNum) {
+ writer.println(String.format("Page %d: %s", pageNum, title)); // Noncompliant
+ }
}
+import java.io.PrintWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-class UserService {
- private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
+class Example {
+ private static final Logger LOG = LoggerFactory.getLogger(Example.class);
void activateUser(String username, String role) {
LOG.info("Activated user '{}' with role '{}'", username, role);
}
-}
-
-For console output, use printf instead of println with String.format. The printf method
-supports the same format specifiers as String.format.
-class Calculator {
- void displayResult(int a, int b, int sum) {
- System.out.println(String.format("Result: %d + %d = %d", a, b, sum)); // Noncompliant
- }
-}
-
-
-class Calculator {
+
void displayResult(int a, int b, int sum) {
System.out.printf("Result: %d + %d = %d%n", a, b, sum);
}
-}
-
-Both PrintStream and PrintWriter provide printf and format methods. Use these instead of
-wrapping String.format in print or println.
-import java.io.PrintWriter;
-
-class ReportGenerator {
- void writeHeader(PrintWriter writer, String title, int pageNum) {
- writer.println(String.format("Page %d: %s", pageNum, title)); // Noncompliant
- }
-}
-
-
-import java.io.PrintWriter;
-class ReportGenerator {
void writeHeader(PrintWriter writer, String title, int pageNum) {
writer.printf("Page %d: %s%n", pageNum, title);
}
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9428.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9428.html
index 7856aaa0221..07471a8874d 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9428.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9428.html
@@ -1,6 +1,5 @@
-This rule raises an issue when a cryptographic digest/hash object is declared as a class-level variable (either instance-level or shared across all
-instances).
-In Java, this specifically refers to MessageDigest instances.
+This rule raises an issue when a MessageDigest object is declared as a class-level variable (either instance-level or shared across
+all instances).
Why is this an issue?
Cryptographic hash computation objects are stateful components that calculate hash values by maintaining internal state as data is incrementally
processed through their methods.
@@ -81,3 +80,4 @@ Standards
An issue is raised when a literal value (numeric, boolean, or other primitive) is converted to a string by concatenating it with an empty string -rather than using explicit conversion methods.
+An issue is raised when a primitive literal is converted to a string by concatenating it with an empty string rather than writing the resulting +string literal directly or using an explicit conversion method.
+This includes compile-time constant contexts when the concatenation can be replaced with an equivalent string literal. Annotation arguments,
+annotation element defaults, constant case labels, and constant field initializers are not exempt.
When the targeted empty-string/primitive-literal pattern is part of a larger compile-time constant concatenation, simplify and report the enclosing
+constant concatenation. For example, report the whole initializer "a" + ("" + 1) and recommend "a1". This rule does not
+target arbitrary constant concatenations such as "a" + "b" unless they contain the targeted pattern.
Converting literals to strings by concatenating them with empty strings is an inefficient and unclear approach.
-When you write an expression that concatenates an empty string with a non-string value, the language runtime must:
-This creates unnecessary intermediate objects and performs extra operations. More importantly, the intent is unclear - a developer reading an empty -string concatenated with a value must infer that this is a type conversion, rather than seeing an explicit conversion call.
-Most languages provide explicit, efficient methods for converting primitives to strings:
+Converting literals to strings by concatenating them with empty strings obscures the intended string value.
+When concatenation is evaluated at runtime, it may involve intermediate objects or additional operations to build the resulting string. The exact +behavior depends on the language, compiler, and runtime. These costs do not apply when the concatenation is evaluated at compile time.
+The readability concern remains in either case: a developer reading an empty string concatenated with a literal must infer the resulting string, +rather than seeing it directly.
+Writing the resulting string literal directly makes the value clear. When an explicit conversion is appropriate, most languages also provide +methods for converting primitives to strings:
These methods directly convert the value to a string without unnecessary intermediate steps, making both the intent and the operation clearer.
-In Java, these explicit methods include Integer.toString(), Boolean.toString(), and other type-specific methods, or the
-generic String.valueOf() method.
These methods make the conversion intent explicit, but do not necessarily preserve compile-time constant semantics.
+In Java, concatenations that are constant expressions are evaluated at compile time. For example, "" + 42 and "a" + ("" +
+1) do not incur runtime string-building overhead. The reason to simplify these expressions is readability, not runtime performance.
The performance impact of using string operations as an indirect method for type conversion is typically small in individual cases, but can -accumulate in large applications or performance-sensitive code paths. More significantly, this pattern reduces code readability and maintainability by -obscuring the developer’s intent.
+This pattern reduces code readability and maintainability by obscuring the intended string value. Runtime concatenation may also have a performance +cost, but concatenations evaluated at compile time do not incur runtime string-building overhead.
Replace empty string concatenation with explicit type conversion methods. For primitive types, use the wrapper class’s static
-toString() method (e.g., Integer.toString(), Long.toString(), Boolean.toString()). Alternatively,
-use String.valueOf() which works for all types.
For compile-time constant concatenations, replace the enclosing constant concatenation with a string literal containing its evaluated value. +Preserve the exact string value, including escaping where necessary.
+Use this replacement in annotation arguments and annotation element defaults (including array elements), constant case labels in
+switch statements or expressions, and constant field initializers. This also applies to implicitly static final fields in
+interfaces and annotation types.
Method calls such as Integer.toString(1) and String.valueOf(1) are not constant expressions. They are unsuitable
+replacements in annotation values, annotation element defaults, and constant case labels because the code would no longer compile. In a
+constant field initializer, they would turn the field into a non-constant, potentially breaking uses in constant contexts and changing constant
+inlining behavior. An equivalent string literal preserves these semantics.
Outside contexts where compile-time constant semantics must be preserved, an explicit conversion is also an option. For primitive types, use the
+wrapper class’s static toString() method (e.g., Integer.toString(), Long.toString(),
+Boolean.toString()) or String.valueOf().
+@interface Label {
+ String value() default "" + 1; // Noncompliant
+ String[] aliases() default {"" + 2}; // Noncompliant
+}
+
+@Label(value = "" + 3, aliases = {"" + 4}) // Noncompliant
+class Example {
+ static final String SIMPLE = "" + 42; // Noncompliant
+ static final String NESTED = "a" + ("" + 1); // Noncompliant: replace the whole initializer with "a1"
+
+ boolean matches(String value) {
+ switch (value) {
+ case "" + 5: // Noncompliant
+ return true;
+ default:
+ return false;
+ }
+ }
+}
+
+
+@interface Label {
+ String value() default "1";
+ String[] aliases() default {"2"};
+}
+
+@Label(value = "3", aliases = {"4"})
+class Example {
+ static final String SIMPLE = "42";
+ static final String NESTED = "a1";
+
+ boolean matches(String value) {
+ switch (value) {
+ case "5":
+ return true;
+ default:
+ return false;
+ }
+ }
+}
+
+When constant semantics do not need to be preserved, explicit conversion methods are another solution:
String s = "" + 123; // Noncompliant String b = "" + true; // Noncompliant @@ -43,6 +97,9 @@Compliant solution
Resources
Documentation