jPinpoint rules for responsible Java coding, sponsored by Rabobank. Uses PMD-7. Problem: A proxy object is created by Contexts and Dependency Injection (CDI) for explicit references, they are not de-referenced implicitly and become a memory leak. Solution: Destroy the reference explicitly. (jpinpoint-rules) 1 Problem: A Calendar is a heavyweight object and expensive to create. Solution: Use Date, Java 8+ java.time.[Local/Zoned]DateTime. (jpinpoint-rules) 3 Interface defines constants. Problem: Possibly exposes implementation details. Solution: Make it a Class which cannot be instantiated, or an Enum. Use static imports. (jpinpoint-rules) 3 Problem: java.text.NumberFormat: DecimalFormat and ChoiceFormat are thread-unsafe. Solution: usual solution is to create a new local one when needed in a method. (jpinpoint-rules) (jpinpoint-rules) 1 Problem: Potential bug: expected to have different assignments in different cases. Solution: assign different values in different cases, common assignments should be taken out of the switch. (jpinpoint-rules) 2 Problem: Internal state can be modified from outside the record, through the implicit accessor method or by the caller of the constructor. Risk of thread-unsafety. Solution: Use the record compact constructor to defensively copy the (possibly) mutable object such as a List, Set or Map, e.g. with List.copyOf(). Make sure to avoid NullPointerExceptions for null values in List.copyOf() methods. Beware that contained objects in collections can itself be mutable. (jpinpoint-rules) 3 list) { } record GoodRecord(String name, List list) { public GoodRecord { list = list != null ? List.copyOf(list) : List.of(); } } ]]> A regular expression is compiled implicitly on every invocation. Problem: this can be expensive, depending on the length of the regular expression. Solution: Compile the regex pattern only once and assign it to a private static final Pattern field. java.util.Pattern objects are thread-safe, so they can be shared among threads. (jpinpoint-rules) 2 5 and (matches(@Image, '[\.\$\|\(\)\[\]\{\}\^\?\*\+\\]+'))) or self::VariableAccess and @Name=ancestor::ClassBody[1]/FieldDeclaration/VariableDeclarator[StringLiteral[string-length(@Image) > 5 and (matches(@Image, '[\.\$\|\(\)\[\]\{\}\^\?\*\+\\]+'))] or not(StringLiteral)]/VariableId/@Name] ]]> Problem: The default constructor of ByteArrayOutputStream creates a 32 bytes initial capacity and for StringWriter 16 chars. Problem: Such a small buffer as capacity usually needs several expensive expansions. Solution: Pre-size the ByteArrayOutputStream or StringWriter with an initial capacity such that an expansion is not needed in most cases, typically much larger than 32, for instance 4096. (jpinpoint-rules) 2 Problem: If a method calls itself (recursion), and it doesn't have a proper and guaranteed stop condition, it may become an infinite loop and result in an OutOfMemoryError or StackOverflowError, and high CPU usage. Solution: Limit the number of recursive calls: use a counter to count up-to or down-from a maximum number of calls, for every recursive call, and stop when the maximum is reached. This maximum should not be a large number. Or better yet, rewrite into iterations for better performance and avoiding errors. (jpinpoint-rules) 1 ', '>=','<', '<=')] (: same method name :) ])]//MethodCall[@MethodName = ancestor::MethodDeclaration/@Name] (: only called on implicit or explicit this :) [(ArgumentList and count(*) = 1) or (ArgumentList and ThisExpression and count(*) = 2)] (: if this has a classType, it must be the same as the first containing class :) [not(exists(ThisExpression/ClassType)) or ThisExpression/ClassType/@SimpleName = ancestor::ClassDeclaration[1]/@SimpleName] (: only if the number of parameters match: not an overloaded method with difference in numbers :) [ArgumentList/@Size = ancestor::MethodDeclaration/FormalParameters/@Size] (: not if any parameter is used in the call in another position :) [not((ArgumentList/*[self::VariableAccess and ((position() != index-of(ancestor::MethodDeclaration/FormalParameters/FormalParameter/VariableId/@Name, concat('', @Name))))]))] (: we check the first 3 arguments on type, which is complicated :) (: if the 1st parameter (if it exists) :) (: its name equals the name of the 1st argument of the called method :) [concat(ArgumentList/*[1]/@Name, '') = ancestor::MethodDeclaration/FormalParameters/FormalParameter[1]/VariableId/@Name or ArgumentList/MethodCall/TypeExpression/ClassType/@SimpleName = ancestor::MethodDeclaration/FormalParameters/FormalParameter[1]/ClassType/@SimpleName or ancestor::MethodDeclaration/FormalParameters[@Size = 0 or FormalParameter[1][( (: or its classType is equal to the local var classtype :) (ClassType/@SimpleName|PrimitiveType/@Kind) = ancestor::MethodDeclaration//LocalVariableDeclaration[ (: which name equals to the name of the 1st argument of the method call :) .//VariableId/@Name = ancestor::MethodDeclaration//MethodCall[@MethodName = ancestor::MethodDeclaration/@Name]/ArgumentList/VariableAccess[1]/@Name ][ (: and which classType is equal to the classtype of the first parameter :) ((ClassType/@SimpleName|PrimitiveType/@Kind) = ancestor::MethodDeclaration/FormalParameters/FormalParameter[1]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]/(ClassType/@SimpleName|PrimitiveType/@Kind) or (: or its classType is equal to the field classtype :) (ClassType/@SimpleName|PrimitiveType/@Kind) = ancestor::ClassBody[1]/FieldDeclaration[ (: which name equals to the name of the 1st argument of the method call :) .//VariableId/@Name = ..//MethodDeclaration//MethodCall[@MethodName = ancestor::MethodDeclaration/@Name]/ArgumentList/VariableAccess[1]/@Name ][ (: and which classType is equal to the classtype of the first parameter :) ((ClassType/@SimpleName|PrimitiveType/@Kind) = ..//MethodDeclaration/FormalParameters/FormalParameter[1]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]]] (: if the 2nd parameter (if it exists) :) [concat(ArgumentList/*[2]/@Name, '') = ancestor::MethodDeclaration/FormalParameters/FormalParameter[2]/VariableId/@Name or ancestor::MethodDeclaration/FormalParameters[@Size <= 1 or FormalParameter[2][( (: or its classType is equal to the local var classtype :) (ClassType/@SimpleName|PrimitiveType/@Kind) = (ancestor::MethodDeclaration//LocalVariableDeclaration|ancestor::ClassBody[1]/FieldDeclaration)[ (: which name equals to the name of the 2nd argument of the method call :) .//VariableId/@Name = ancestor::MethodDeclaration//MethodCall[@MethodName = ancestor::MethodDeclaration/@Name]/ArgumentList/VariableAccess[2]/@Name][ (: and which classType is equal to the classtype of the 2nd parameter :) ((ClassType/@SimpleName|PrimitiveType/@Kind) = ancestor::MethodDeclaration/FormalParameters/FormalParameter[2]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]/(ClassType/@SimpleName|PrimitiveType/@Kind) or (: or its classType is equal to the field classtype :) (ClassType/@SimpleName|PrimitiveType/@Kind) = ancestor::ClassBody[1]/FieldDeclaration[ (: which name equals to the name of the 2nd argument of the method call :) .//VariableId/@Name = ..//MethodDeclaration//MethodCall[@MethodName = ancestor::MethodDeclaration/@Name]/ArgumentList/VariableAccess[2]/@Name ][ (: and which classType is equal to the classtype of the 2nd parameter :) ((ClassType/@SimpleName|PrimitiveType/@Kind) = ..//MethodDeclaration/FormalParameters/FormalParameter[2]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]]] (: if the 3rd parameter (if it exists) :) [concat(ArgumentList/*[3]/@Name, '') = ancestor::MethodDeclaration/FormalParameters/FormalParameter[3]/VariableId/@Name or ancestor::MethodDeclaration/FormalParameters[@Size <= 2 or FormalParameter[3][( (: or its classType is equal to the local var classtype :) (ClassType/@SimpleName|PrimitiveType/@Kind) = (ancestor::MethodDeclaration//LocalVariableDeclaration|ancestor::ClassBody[1]/FieldDeclaration)[ (: which name equals to the name of the 3rd argument of the method call :) .//VariableId/@Name = ancestor::MethodDeclaration//MethodCall[@MethodName = ancestor::MethodDeclaration/@Name]/ArgumentList/VariableAccess[3]/@Name][ (: and which classType is equal to the classtype of the 3rd parameter :) ((ClassType/@SimpleName|PrimitiveType/@Kind) = ancestor::MethodDeclaration/FormalParameters/FormalParameter[3]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]/(ClassType/@SimpleName|PrimitiveType/@Kind) or (: or its classType is equal to the field classtype :) (ClassType/@SimpleName|PrimitiveType/@Kind) = ancestor::ClassBody[1]/FieldDeclaration[ (: which name equals to the name of the 3rd argument of the method call :) .//VariableId/@Name = ..//MethodDeclaration//MethodCall[@MethodName = ancestor::MethodDeclaration/@Name]/ArgumentList/VariableAccess[3]/@Name ][ (: and which classType is equal to the classtype of the 3rd parameter :) ((ClassType/@SimpleName|PrimitiveType/@Kind) = ..//MethodDeclaration/FormalParameters/FormalParameter[3]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]/(ClassType/@SimpleName|PrimitiveType/@Kind)) ]]] ]]> 0 ) { delay(10, MILLISECONDS); foo(--attemptsLeft); } } } class IterationsBetter { private static final int MAX_ATTEMPTS = 5; private void foo() { int attempt = 0; boolean success = false; while (!success && attempt++ < MAX_ATTEMPTS) { success = tryRemoteCall(); if (!success) { delay(10, MILLISECONDS); } } } } ]]> Problem: Files.readAllBytes and Files.readAllLines load all bytes from a file into the heap memory. This may result in an OutOfMemoryError crash, or long gc pauses and slow responses. Solution: Stream-through: use streaming all the way, don't store the whole thing in memory, don't use byte arrays. Often, functionality can be achieved in a streaming way. Note: not a problem for small files. (jpinpoint-rules) 2 fileLines = Files.readAllLines(path); // bad // process bytes / lines } } class Good { void good(Path in) throws IOException { try (BufferedReader reader = Files.newBufferedReader(in)) { String line = reader.readLine(); // process line by line } } } ]]> Problem: Lombok annotations for fields [@Getter, @Setter, @EqualsAndHashCode, @Value, @Data] are of no use and confusing in case the class has no fields. Solution: Remove the Lombok annotation. (jpinpoint-rules) 4 Multiple statements concatenate to the same String. Problem: Each statement with one or more +-operators creates a hidden temporary StringBuilder, a char[] and a new String object, which all have to be garbage collected. Solution: Use StringBuilder.append. (jpinpoint-rules) 2 Problem: A list which is unnecessarily mutable may accidentally be added to and cause a memory leak. Solution: For the list which is not actually modified, make it impossible to modify after object construction/initialization: use Java 9 List.of, Java 11 List.copyOf, Collections.unmodifiableList or Guava ImmutableList. (jpinpoint-rules) 3 Problem: the time to find element is O(n); n = the number of enum values. This identical processing is executed for every call. Considered problematic when n > 3. Solution: use a static field-to-enum-value Map. Access time is O(1), provided the hashCode is well-defined. For one String field, usually toString returns that field. Implement a fromString method to provide the reverse conversion by using the map. (jpinpoint-rules) 3 3]//MethodDeclaration/Block //MethodCall[pmd-java:matchesSig('java.util.stream.Stream#findFirst()') or pmd-java:matchesSig('java.util.stream.Stream#findAny()')] [//MethodCall[pmd-java:matchesSig('java.util.stream.Stream#of(_)') or pmd-java:matchesSig('java.util.Arrays#stream(_)')] [ArgumentList/MethodCall[pmd-java:matchesSig('_#values()')]]] ]]> fromString(String name) { return Stream.of(values()).filter(v -> v.toString().equals(name)).findAny(); // bad: iterates for every call, O(n) access time } } Usage: `Fruit f = Fruit.fromString("banana");` // GOOD public enum Fruit { APPLE("apple"), ORANGE("orange"), BANANA("banana"), KIWI("kiwi"); private static final Map nameToValue = Stream.of(values()).collect(toMap(Object::toString, v -> v)); private final String name; Fruit(String name) { this.name = name; } @Override public String toString() { return name; } public static Optional fromString(String name) { return Optional.ofNullable(nameToValue.get(name)); // good, get from Map, O(1) access time } } ]]> A regular expression is compiled on every invocation. Problem: this can be expensive, depending on the length of the regular expression. Solution: Usually a pattern is a literal, not dynamic and can be compiled only once. Assign it to a private static field. java.util.Pattern objects are thread-safe so they can be shared among threads. (jpinpoint-rules) 2 XPathExpression is created and compiled on every method call, compiled possibly implicitly by XPath::evaluate. Problem: Creation of XPath and compilation of XPathExpression takes time. It may slow down your application. Solution: 1. Avoid XPath usage. 2. Compile the xpath expression as String into a XPathExpression. However, since XPath and XPathExpression classes are thread-unsafe, they are not easily cached. Caching it in a ThreadLocal may be a solution. (jpinpoint-rules) 2 tlFac = ThreadLocal.withInitial(XPathFactory::newInstance); private static final ThreadLocal tlExpr; static { XPath xpath = tlFac.get().newXPath(); try { XPathExpression expr = xpath.compile("//book[author='Isaac Asimov']/title/text()"); tlExpr = ThreadLocal.withInitial(() -> expr); // good } catch (XPathExpressionException e) { throw new RuntimeException(e); } } public static NodeList good(Document doc) throws XPathExpressionException { return (NodeList) tlExpr.get().evaluate(doc, XPathConstants.NODESET); // good } } ]]> Problem: Recreating a DateTimeFormatter is relatively expensive. Solution: Java 8+ java.time.DateTimeFormatter is thread-safe and can be shared among threads. Create the formatter from a pattern only once, to initialize a static final field. (jpinpoint-rules) 2 Problem: Creating a security provider is expensive because of loading of algorithms and other classes. Additionally, it uses synchronized which leads to lock contention when used with multiple threads. Solution: This only needs to happen once in the JVM lifetime, because once loaded, the provider is typically available from the Security class. Create the security provider only once: only in case it is nog available from the Security class, yet. (jpinpoint-rules) 2 Problem: Reflection is relatively expensive. Solution: Avoid to use reflection. Use the non-reflective, explicit way, like generation by IDE or Lombok. (jpinpoint-rules) 2 Problem: java.util.SimpleDateFormat is thread-unsafe. The usual solution is to create a new one when needed in a method. Creating SimpleDateFormat is relatively expensive. Solution: Use java.time.DateTimeFormatter. These classes are immutable, thus thread-safe and can be made static. (jpinpoint-rules) 2 Problem: StringBuffer introduces locking overhead because it is thread safe. Its thread-safety is rarely needed. Solution: Replace StringBuffer by StringBuilder. (jpinpoint-rules) 3 class Foo { public void bad() { var sb = new StringBuffer(); // bad } public void good() { var sb = new StringBuilder(); // good } } Problem: Time unit like hours, seconds, milliseconds is not specified and may be assumed differently by readers. Different assumptions will lead to errors or hidden problems like ineffective caches. Solution: Specify the time unit in the identifier, like connectTimeoutMillis. (jpinpoint-rules) 3 A String to be logged is built unconditionally. Problem: String building, concatenation and/or other operations happen before the debug, trace or info method executes, so independent of the need to actually log. Concatenation is relatively expensive. Solution: Build the String conditionally on the log level, within an if statement. (jpinpoint-rules) 2 Problem: Creating a StringBuilder and using append is more verbose, less readable and less maintainable than simply using String concatenation (+). For one statement resulting in a String, creating a StringBuilder and using append is not faster than simply using concatenation. Solution: Simply concatenate Strings in one statement, it is more concise, better readable and more maintainable. (jpinpoint-rules) 3 The XPathExpression targets a wide scope since it starts with `//`. Problem: XPath has to search in a wide scope for occurrences, this may take a while. Solution: 1. Avoid XPath usage. 2. Make the scope as narrow as possible, do not start with `//`. (jpinpoint-rules) 2 XPathAPI is used. Problem: XPathAPI implementation is slow. Solution: 1. try to avoid using XPathAPI. 2. improve performance by using jvm parameters and possibly CachedXPathAPI. (jpinpoint-rules) 3 XPath is used. Problem: XPath implementation is slow. Solution: 1. avoid using XPath. 2. improve performance by using jvm parameters and possibly Cached XPath API. (jpinpoint-rules) 3 Problem: With FileInputStream and FileOutputStream, file access is not buffered. The stream is read-from/written-to file byte by byte, where each operating system call has its overhead, which makes it slow. Solution: Use buffering to read/write a chunk of bytes at once with much lower overhead. Use BufferedOutput/InputStream which has a buffer size of 8 kB by default to write at once. Make sure to close it after use. (jpinpoint-rules) 3 Problem: Files.newInputStream or Files.newOutputStream is not buffered. The stream is read from/written to file byte by byte, where each operating system call has its overhead which makes it slow. Solution: Use buffering to read/write a chunk of bytes at once with much lower overhead. Use e.g. BufferedInputStream or BufferedOutputStream which has a buffer size of 8 kB to read/write at once. Make sure to close it after use. Note that IOUtils methods take care of buffering the inputStream. (jpinpoint-rules) 3 Problem: Equal objects may have different hashCodes and end-up in different buckets of a Map/Set. Strange things can happen like adding an object to a Set and not being able to find it back. Solution: When objects are equal, hashCode needs to be equal, too. Use the same fields in equals and hashCode and use identical conversions like toUpperCase() in both when needed. Don't use equalsIgnoreCase. (jpinpoint-rules) (jpinpoint-rules) 1 (@Name = ancestor::ClassBody[1]//MethodDeclaration[@Name='hashCode' and (FormalParameters/@Size = 0)]/Block//MethodCall[pmd-java:matchesSig('java.lang.String#toLowerCase()')]//VariableAccess/@Name) ] (: go to violating toLowerCase in equals :) /ancestor::ClassBody[1]//MethodDeclaration[@Name='equals']/Block//MethodCall[pmd-java:matchesSig('java.lang.String#toLowerCase()')] , (: the number of toUpperCase on a field used in equals should not be larger than that of hashCode :) //FieldDeclaration//VariableId[ (@Name = ancestor::ClassBody[1]//MethodDeclaration[@Name='equals' and (FormalParameters/@Size = 1)]/Block//MethodCall[pmd-java:matchesSig('java.lang.String#toUpperCase()')]//VariableAccess/@Name) > (@Name = ancestor::ClassBody[1]//MethodDeclaration[@Name='hashCode' and (FormalParameters/@Size = 0)]/Block//MethodCall[pmd-java:matchesSig('java.lang.String#toUpperCase()')]//VariableAccess/@Name) ] (: go to violating toUpperCase in equals :) /ancestor::ClassBody[1]//MethodDeclaration[@Name='equals']/Block//MethodCall[pmd-java:matchesSig('java.lang.String#toUpperCase()')] , (: equalsIgnoreCase on field, in equals, not allowed :) //MethodDeclaration[@Name='equals' and (FormalParameters/@Size = 1)]/Block//MethodCall[pmd-java:matchesSig('java.lang.String#equalsIgnoreCase(java.lang.String)')] ]]> Problem: Equal objects may have different hashCodes and end-up in different buckets of a Map/Set. Strange things can happen like adding an object to a Set and not being able to find it back. Solution: When objects are equal, hashCode needs to be equal, too. Use the same fields in equals and hashCode. (jpinpoint-rules) 1 (@Name = ancestor::ClassBody[1]//MethodDeclaration[@Name='hashCode' and FormalParameters/@Size = 0]/Block//MethodCall[@MethodName='toLowerCase']/(FieldAccess|VariableAccess)/@Name) ] | //FieldDeclaration/VariableDeclarator[ (@Name = ancestor::ClassBody[1]//MethodDeclaration[@Name='equals' and FormalParameters/@Size = 1]/Block//MethodCall[@MethodName='toUpperCase']/(FieldAccess|VariableAccess)/@Name) > (@Name = ancestor::ClassBody[1]//MethodDeclaration[@Name='hashCode' and FormalParameters/@Size = 0]/Block//MethodCall[@MethodName='toUpperCase']/(FieldAccess|VariableAccess)/@Name) ] ]]> Problem: when equals is implemented, hashCode needs to be implemented, too; and if objects are equal, hashCode needs to be equal, too. If hashCode only calls super.hashCode, it is effectively not implemented. Strange things can happen, for instance, if super.hashCode calls Object.hashCode, it is unique for each object and when used in Set or as key in Map, equal objects can duplicate in Set, and keys not found in Map. Solution: implement hashCode properly, consistent with equals. Meet the equals and hashCode contracts. If objects are equal, hashCode needs to be equal, too. See Effective Java. (jpinpoint-rules) 1 Problem: If equals and hashCode are not defined, they don't meet the programmer's expectations and the requirements for use with the collections API. It may result in unexpected, undesired behavior. Solution: Add proper equals and hashCode methods that meet the equals-hashCode contract to all objects which might anyhow be put in a Map, Set or other collection. Or use Lombok @EqualsAndHashCode, @Value or @Data. Also holds for inner classes. If the object should never be checked for equality or used in a collection, also add those methods and let them throw UnsupportedOperationException to fail fast. @Xml... , @Entity, Throwable and Executor and some other objects are ignored because they are assumed to be not used as value objects. (jpinpoint-rules) 3 3 or starts-with(@Name, 'is') and string-length(@Name) > 2)] [ClassType/@SimpleName=ancestor::ClassBody[1]/FieldDeclaration[@Static=false()]/ClassType/@SimpleName] ) (: and class has no equals nor hashCode method :) and (not (ancestor::ClassBody[1]/MethodDeclaration[(@Visibility='public' or ../../@Nested=true()) and @Static=false() and (@Name='equals' and .//FormalParameters[@Size=1]) or (@Name='hashCode' and .//FormalParameters[@Size=0])])) (: and class has a toString and #fields <= 1+#getters :) and ((ancestor::ClassBody[1]//MethodDeclaration[(@Visibility='public' or ../../@Nested=true()) and @Static=false() and @Name='toString'] and count(ancestor::ClassBody[1]/FieldDeclaration[@Static=false()]) <= (1 + count(ancestor::ClassBody[1]/MethodDeclaration[(@Visibility='public' or ../../@Nested=true()) and @Static=false() and (starts-with(@Name, 'get') and string-length(@Name) > 3 or starts-with(@Name, 'is') and string-length(@Name) > 2)])) or ancestor::ClassDeclaration[1][ends-with(upper-case(@SimpleName), 'DTO')]) (: or #fields == #getters :) or count(ancestor::ClassBody[1]/FieldDeclaration[@Static=false()]) = count(ancestor::ClassBody[1]/MethodDeclaration[(@Visibility='public' or ../../@Nested=true()) and @Static=false() and (starts-with(@Name, 'get') and string-length(@Name) > 3 or starts-with(@Name, 'is') and string-length(@Name) > 2)]) ) ] (:-up to class -:) ]/.. ]]> Problem: Variables like 'var3' and fields like 'FOUR = 4', do not express what they are used for. This is bad for maintainability. Solution: Let variable names express what they are used for, like 'key' and 'MAX_KEYS = 4' (jpinpoint-rules) 3 Problem: Creating Comparator instances repeatedly in methods like compareTo or sort calls is inefficient. Solution: Initialize the Comparator once as a static final field and reuse. (jpinpoint-rules) 2 { @Override public int compareTo(@NotNull Person o) { return Comparator.comparing(Person::getFirstName) // Bad: Creates new Comparator instance on each invocation .thenComparing(Person::getLastName) .thenComparingInt(Person::getAge) .compare(this, o); } } public class GoodPerson implements Comparable { private static final Comparator COMPARE_FIRST_LAST_NAME_AGE = Comparator.comparing(Person::getFirstName) .thenComparing(Person::getLastName) .thenComparingInt(Person::getAge); @Override public int compareTo(@NotNull Person o) { return COMPARE_FIRST_LAST_NAME_AGE.compare(this, o); // Good: Comparator initialized once as static final field } } ]]> Problem: the JodaTime and ThreeTenBp libraries have non-optimal performance and memory usage. Solution: Migrate to java.time for a modern solution with better performance and less memory usage. If you are stuck on Java 6 or 7, ThreeTenBp is the best option, then suppress the violation. (jpinpoint-rules) 2 MDC values are added for logging, but not removed. Problem: MDC values can leak to other user transactions (requests) and log incorrect information. Solution: remove the MDC value in a finally clause. (jpinpoint-rules) 2 violation :) and not ((ancestor::MethodDeclaration/@Visibility!='private' (: or in a method called from a non-try scope -> violation:) or ancestor::MethodDeclaration/@Name = ancestor::ClassBody//MethodDeclaration[not(.//TryStatement)]//MethodCall/@MethodName) (: or in a method not called from a try statement with a finally -> violation :) or not(ancestor::MethodDeclaration/@Name=ancestor::ClassBody//TryStatement[.//FinallyClause]/Block//MethodCall/@MethodName) (: or in a method called from a try statement and the key not removed / cleared in finally :) or (ancestor::MethodDeclaration/@Name=ancestor::ClassBody//TryStatement[.//FinallyClause]/Block//MethodCall/@MethodName and not( (: direct remove in the same try's finally :) @Image = ancestor::ClassBody//TryStatement/FinallyClause//MethodCall[pmd-java:matchesSig('org.slf4j.MDC#remove(java.lang.String)')]/ArgumentList/*[1]/@Image or ancestor::ClassBody//TryStatement//FinallyClause//MethodCall[pmd-java:matchesSig('org.slf4j.MDC#clear()')] (: or remove/clear inside a method that is called from that finally (Issue #653) :) or @Image = ancestor::ClassBody//MethodDeclaration[ @Name = ancestor::ClassBody//TryStatement[.//Block//MethodCall/@MethodName = ancestor::MethodDeclaration/@Name]/FinallyClause//MethodCall/@MethodName ]//MethodCall[pmd-java:matchesSig('org.slf4j.MDC#remove(java.lang.String)')]/ArgumentList/*[1]/@Image or ancestor::ClassBody//MethodDeclaration[ @Name = ancestor::ClassBody//TryStatement[.//Block//MethodCall/@MethodName = ancestor::MethodDeclaration/@Name]/FinallyClause//MethodCall/@MethodName ]//MethodCall[pmd-java:matchesSig('org.slf4j.MDC#clear()')] ) )) )] ]]> Problem: If only the @Setter and/or @Getter annotation is used, and equals and hashcode are not defined, this is probably a mistake. The object has fields and if used in a collection or with equality, it will not behave as expected. Solution: include equals and hashCode e.g. by @EqualsAndHashCode or usually better @Data instead of @Setter/@Getter, however, are setters really needed? Prefer immutability, so @Value, combined with @Builder, with @Singular for collection fields. (jpinpoint-rules) 3 Problem: If a field which can be assigned separately (independent of other fields) is missing in the equals method, then changing the field in one object has no effect on the equality with another object. However, if a field of one of two equal objects is changed, the expectation is that they are no longer equal. Solution: include the missing field in the equals and hashCode method. (jpinpoint-rules) 3 Problem: Two unequal objects can have the same hashCode and end up in the same bucket of a Map. This may result in bad performance, O(n) lookup instead of O(1). Solution: Use the same fields in hashCode as are used in equals. (jpinpoint-rules) 2 (@Name = ancestor::ClassBody[1]//MethodDeclaration[@Name='hashCode' and FormalParameters/@Size = 0]/Block//(FieldAccess|VariableAccess)/@Name) ]/VariableId ]]> Problem: If multiple entries end up in the same HashMap bucket, they are stored as LinkedList, and with more than 7 as a red black tree. The list access time is O(n) and tree access time is only O(log n) which is much faster for large n. This tree implementation utilizes the compareTo from the Comparable interface. If this is not implemented, access will be slow. Solution: Implement Comparable for your Map keys. Do not use classes for the keys which don't implement Comparable, like Thread, Class and Object. At least not for Maps which can grow large. Note that equals and hashCode must be implemented properly for the keys, and compareTo must be compatible with equals. (jpinpoint-rules) 3 oMap; // bad, Object does not implement Comparable Map tMap; // bad, Thread does not implement Comparable Map oldStyleMap = new HashMap(); // cannot check here void putInOldStyleBad() { oldStyleMap.put(new Thread(), "value"); // bad } Map cMap; // good void putInOldStyleGood() { oldStyleMap.put("key", "value"); } } ]]> Problem: A `Set` is commonly implemented with a `Map`. If multiple entries end up in the same `HashMap` bucket, they are stored as `LinkedList`, and with more than 7 as a red-black tree. The list access time is `O(n)` and tree access time is only `O(log n)` which is much faster for large `n`. This tree implementation utilizes the `compareTo` from the `Comparable` interface. If this is not implemented, access by element will be slow. Iterating through the elements does not suffer from this slow access because no lookup by key in the map is involved. Access methods by element which are affected: `contains[All]`, `retainAll`, `remove[All]`. Solution: Implement `Comparable` for your `Set` elements to ensure optimal performance. Or use a `Set` with a `Comparator` in the constructor (for instance, `TreeSet` or `ConcurrentSkipListSet`). When using Sets with non-Comparable types like `Thread`, `Class`, or `Object`, avoid large Sets especially for element access operations. If you must use such types, apply `@SuppressWarnings` with a clear explanation only when the `Set` is guaranteed to stay small (under 7 elements per bucket) with minimal growth risk. Remember that small Sets can grow unexpectedly over time. Note: `equals` and `hashCode` must be implemented properly for the elements, and `compareTo` must be compatible with `equals`. (jpinpoint-rules) 3 HTTP_ROUTE_COMPARATOR = Comparator.comparing((HttpRoute route) -> route.getTargetHost().getHostName()) .thenComparingInt(route -> route.getTargetHost().getPort()); Set strSet = new HashSet<>(); List strList = new ArrayList<>(); Set fieldRouteSet = new HashSet<>(); // bad Set fieldRouteSetComp = new TreeSet<>(HTTP_ROUTE_COMPARATOR); // good: with explicit comparator List routeList = new ArrayList<>(); void byElemBad(Set paramRouteSet) { // probably bad, but cannot tell the actual constructor of the Set paramRouteSet.retainAll(routeList); HttpRoute firstRoute = paramRouteSet.iterator().next(); fieldRouteSet.contains(firstRoute); Set localRouteSet = new HashSet<>(); // bad localRouteSet.removeAll(routeList); } void otherCasesGood(Set paramRouteSet) { // probably bad, but cannot tell the actual constructor of the Set strSet.contains("bla"); strSet.retainAll(strList); strSet.remove("other"); HttpRoute firstRoute = paramRouteSet.iterator().next(); fieldRouteSetComp.remove(firstRoute); } } ]]> Problem: A HashMap and HashSet are rather greedy in memory usage. Solution: Use an EnumMap or EnumSet. It is represented internally with arrays which is extremely compact and efficient. (jpinpoint-rules) 3 map = new EnumMap<>(YourEnumType.class); Set set = EnumSet.allOf(YourEnumType.class); ]]> Problem: String concatenation (+) is executed regardless of log level and can be expensive. Solution: Use SLF4J formatting with {}-placeholders or log and format conditionally. (jpinpoint-rules) 2 Problem: Creation of a log argument with a toString or other operation(s) may be expensive, while depending on the log level, the result may not be used. Solution: Create the log argument conditionally on the log level, within an if statement. For just 'obj.toString()', just pass 'obj' to the log method and leave it to SLF4J to call toString() only if needed. (jpinpoint-rules) 2 Problem: An operation is executed regardless of log level. This could be much processing while the result is typically not used. Detected are obj.toString() and operations with one or more arguments except usually cheap obj.getXXX(arg). Solution: Execute the operation only conditionally and utilize SLF4J formatting with {}-placeholders. (jpinpoint-rules) 2 0 arguments or toString(); except getters which are assumed as fast :) /ArgumentList//MethodCall[ (ArgumentList[@Size>0] or @MethodName='toString') and not(starts-with(@MethodName, 'get')) (: if methods are chained, only report 1 in the chain :) and not(ancestor::MethodCall[ArgumentList[@Size>0]]/MethodCall) ] (: except if method name contains log, trace, debug or info (case insensitive), and it is non-public :) [not(ancestor::MethodDeclaration [matches(@Name, 'trace|debug|info|log','i') and @Visibility = 'private'] (: and the method is called only conditionally: it is called inside an if and not prefixed with || :) [@Name=ancestor::ClassBody//MethodCall[ancestor::IfStatement//MethodCall[ @MethodName = ('isTraceEnabled', 'isDebugEnabled','isInfoEnabled', 'isLoggable') and count(../../InfixExpression[@Operator='||' or @Operator='|' or @Operator='^']) = 0 ]]/@MethodName] (: and it is not called not inside an if :) [not(@Name=ancestor::ClassBody//MethodCall[ not( ancestor::IfStatement//MethodCall[ @MethodName = ('isTraceEnabled', 'isDebugEnabled','isInfoEnabled', 'isLoggable') and count(../../InfixExpression[@Operator='||' or @Operator='|' or @Operator='^']) = 0 ] ) ]/@MethodName) ] )] ]]> Problem: The suppressed rule detects problems, suppressing the detected violations without full knowledge can lead to the problems the rule is trying to prevent. Solution: Only suppress violations (warnings) when you have complete understanding of the rule, and you are sure it does not apply. When suppressing warnings, document your reasoning and report false positives to the rule maintainers to help improve rule accuracy. Note: This rule is just informational to track use of SuppressWarnings, to be able to double-check if suppression is correct. It often doesn't need a fix. (jpinpoint-rules) 5 Problem: The suppressed rule detects high risk problems, suppressing the detected violations without full knowledge can lead to incidents like customer data mix-up, corrupt data, server crashes or very bad performance. Solution: Only suppress violations (warnings) when you have complete understanding of the rule, and you are sure it does not apply. When suppressing warnings, document your reasoning and report false positives to the rule maintainers to help improve rule accuracy. Note: This rule is to track use of SuppressWarnings for high risk rule violations, to be able to double-check if suppression is correct. It doesn't necessarily need a fix. (jpinpoint-rules) 4 customerOrderList; // a bad idea to have in a singleton, should be solved instead of suppressed //.. } ]]> Problem: Use of FileItem.get and FileItem.getString could exhaust memory since they load the entire file into memory Solution: Use streaming methods and buffering. (jpinpoint-rules) 2 Problem: A Calendar is a heavyweight object and expensive to create. Solution: Use 'new Date()', Java 8+ java.time.[Local/Zoned]DateTime.now(). (jpinpoint-rules) 2 Concatenation of Strings is used inside an StringBuilder.append argument. Problem: Each statement with one or more +-operators creates a hidden temporary StringBuilder, a char[] and a new String object, which all have to be garbage collected. Solution: Use an extra fluent append instead of concatenation. (jpinpoint-rules) 2 0 and not(..//VariableAccess/@Name = ancestor::ClassBody//VariableId[@Final=true()]/@Name)]] ] ]]> A String is built in a loop by concatenation. Problem: Each statement with one or more +-operators creates a hidden temporary StringBuilder, a char[] and a new String object, which all have to be garbage collected. Solution: Use the StringBuilder append method. (jpinpoint-rules) 2 values = Arrays.asList("tic ", "tac ", "toe "); for (String val : values) { log += val; } return log; } private String good(String arg) { StringBuilder sb = new StringBuilder(); List values = Arrays.asList("tic ", "tac ", "toe "); for (String val : values) { sb.append(val); } return sb.toString(); } } ]]> Problem: Streams is a paradigm based on functional programming: the result should depend only on its input and not update any state. Use of forEach is actually iterative code masquerading as streams code. It is typically harder to read and less maintainable than the iterative form. For parallel streams, side effects are dangerous: accessing a thread-unsafe shared variable is a concurrency bug. Solution: Use the for-each (enhanced-for) loop, internal iterator, or the pure functional form. The forEach operation should only be used to report (i.e. log) the result of a stream computation. (jpinpoint-rules) 4 letters = List.of("D", "O", "G", "E"); Map map = new HashMap<>(); // forEach in stream, bad letters.stream().forEach(l -> map.put(l, 0)); // bad, side effect, modifies map // pure functional form, good map = letters.stream().collect(toMap(l -> l, v -> 0)); // reporting result by logging, good letters.stream().forEach(Log::info); // external iterative form, meant for modifying state, good for (String l : letters) { map.put(l, 0); } // internal iterator, fine for modifying state, good letters.forEach(l -> map.put(l, 0)); ]]> Problem: Streams is a paradigm based on functional programming: the result should depend only on its input, and the stream should not update any state: not modify any variable. By the spec, everything that does not contribute to the result, may be optimized away. So, side effects are not guaranteed to be executed! For parallel streams, side effects are dangerous: accessing a thread-unsafe shared variable is a concurrency bug. Accessing a (thread-safe) shared variable may cause data mix-up between the threads. Solution: Use the pure functional form: return a result based just on the input; do not modify any variable. (jpinpoint-rules) 3 getEndpointsInfo(String... endpoints) { AtomicReference currentEndpoint = new AtomicReference<>(); return Arrays.stream(endpoints) .peek(endpoint -> currentEndpoint.set(endpoint)) // bad .peek(endpoint -> log.debug(endpoint)) // peek is meant for something like this .map(String::toLowerCase) .map(pingInfo -> addEndpointInfo(pingInfo, currentEndpoint.get())) .toList(); } ]]> Problem: Field in a Serializable class is not serializable nor transient. When (de)serialization happens, a RuntimeException will be thrown and (de)serialization fails. Solution: make the field either transient, make its class implement Serializable or interface extend Serializable. Note: Classes extending Throwable do, by inheritance, implement Serializable, yet are excluded in this rule, since they are typically never actually serialized. An exception to this exception is when extending RemoteException, then fields should be transient or serializable. (jpinpoint-rules) 2 ) :) and ( not(exists(.//TypeArguments)) or exists(.//TypeArguments/ClassType[ not(pmd-java:typeIs('java.io.Serializable')) (: and can be resolved :) and pmd-java:typeIs('java.lang.Object') ] ) ) and ancestor::ClassDeclaration[pmd-java:typeIs('java.io.Serializable') (: Throwable is the exception, except RemoteException :) and not( pmd-java:typeIs('java.lang.Throwable') and not(pmd-java:typeIs('java.rmi.RemoteException')) ) ] ] ]]> listOfStrings = new ArrayList(); List listOfThreads = new ArrayList(); // bad Map mapToString = new HashMap(); Map mapToThread = new HashMap(); //bad } class Bar extends Exception { Thread t1NotMeets; transient Thread t2meets; } class Baz extends RemoteException { Thread t1NotMeets; // bad transient Thread t2meets; } ]]> Problem: lambda expressions with deep nesting (lambda's in lambda's) are hard to understand and maintain. Solution: extract the lambda expression code block(s) into one or more well-named separate method(s). Note: A violation when the depth of lambda-with-code-block nesting exceeds (by default) 1, or the depth of lambda-single-expression in lambda nesting exceeds (by default) 4. (jpinpoint-rules) 3 number($param-block-max)] | //LambdaExpression[*[@Expression=true()]][count(ancestor::LambdaExpression) > number($param-single-max)] ]]> single0(b) .orElseGet(() -> single1(b) // single nesting level 1 .orElseGet(() -> single2(b)// 2 .orElseGet(() -> single3(b)// 3 .orElseGet(() -> singlet4(b)// 4 .orElseGet(() -> single5(b)// 5 // bad .orElseGet(() -> single6(b))))))));// 6 // bad } public Object blockLambdas(String a, String b) { return detect(a) .orElseGet(() -> single0(b) .orElseGet(() -> {block0(b) // block nesting level 0 .orElseGet(() -> {block1(b)// 1 .orElseGet(() -> {block2(b)// 2 // bad .orElseGet(() -> single4(b)// single nesting level 4 // good .orElseGet(() -> single5(b)));});});}));// single nesting level 5, bad } } ]]> Problem: lambda expressions with many statements are hard to understand and maintain. Solution: extract the lambda expression code block into one or more well-named separate method(s). (jpinpoint-rules) 3 number($param-max)] ]]> { if (entity.getKNr() != null) { action.withActions( new Action().withFoId(actionFoId.incrementAndGet()) .withEntityPlanFoId(1) .withEntity(mapLogicType(true)) ); } }); } void baz(List additionals) { additionals.forEach(entity -> { // bad, too many statements (5) if (entity.getKNr() != null) { action.withActions( new Action().withFoId(actionFoId.incrementAndGet()) .withEntityPlanFoId(1) .withEntity(mapLogicType(true)) ); } else { action.withActions(new Action().withFoId(0)); } }); } } ]]> Importing a class statically allows you to use its public static members easily: without qualifying them with the class name. Problem: if you import too many classes statically, your code can become confusing and difficult to maintain. The default maximum = 3. Solution: Import class members individually. (jpinpoint-rules) 2 number($param-max)] ]]> Problem: When analyzing code, PMD could not resolve a used type (class, annotation or other type). Several rules make use of type information and assume it is available. If not, those rules typically fail to work correctly and will for instance show false positives. Solution: Make sure all types can be resolved: compile all source code, process all annotations (like lombok), compile generated code, make all dependencies available on the classpath, etc. Note: this rule is still somewhat experimental and may show false positives. (jpinpoint-rules) 5 Problem: Blocking calls, for instance remote calls, may exhaust the common pool for some time thereby blocking all other use of the common pool. In addition, nested use of the common pool can lead to deadlock. Do not use the common pool for blocking calls. The parallelStream() call uses the common pool. Solution: Use a dedicated thread pool with enough threads to get proper parallelism. The number of threads in the common pool is equal to the number of CPUs and meant to utilize all of them. It assumes CPU-intensive non-blocking processing of in-memory data. (jpinpoint-rules) 1 list = new ArrayList(); final ForkJoinPool myFjPool = new ForkJoinPool(10); final ExecutorService myExePool = Executors.newFixedThreadPool(10); void bad1() { list.parallelStream().forEach(elem -> storeDataRemoteCall(elem)); // bad } void good1() { CompletableFuture[] futures = list.stream().map(elem -> CompletableFuture.supplyAsync(() -> storeDataRemoteCall(elem), myExePool)) .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures).get(10, TimeUnit.MILLISECONDS)); } void good2() throws ExecutionException, InterruptedException { myFjPool.submit(() -> list.parallelStream().forEach(elem -> storeDataRemoteCall(elem)) ).get(); } String storeDataRemoteCall(String elem) { // do remote call, blocking. We don't use the returned value. RestTemplate tmpl; return ""; } } ]]> Problem: Future.supplyAsync is typically used for remote calls. By default, it uses the common pool. The number of threads in the common pool is equal to the number of CPU's, which is suitable for in-memory processing. For I/O, however, this number is typically not suitable because most time is spent waiting for the response and not in CPU. The common pool must not be used for blocking calls. Solution: A separate, properly sized, pool of threads (an Executor) should be used for the async calls. (jpinpoint-rules) 1 >[] futures = accounts.stream() .map(account -> CompletableFuture.supplyAsync(() -> isAccountBlocked(account))) // bad .toArray(CompletableFuture[]::new); } void good() { CompletableFuture>[] futures = accounts.stream() .map(account -> CompletableFuture.supplyAsync(() -> isAccountBlocked(account), asyncPool)) // good .toArray(CompletableFuture[]::new); } } ]]> Problem: take() stalls indefinitely in case of hanging threads and consumes a thread. Solution: use poll() with a timeout value and handle the timeout. (jpinpoint-rules) 2 void collectAllCollectionReplyFromThreads(CompletionService> completionService) { try { Future> futureLocal = completionService.take(); // bad Future> futuresGood = completionService.poll(3, TimeUnit.SECONDS); // good responseCollector.addAll(futuresGood.get(10, TimeUnit.SECONDS)); // good } catch (InterruptedException | ExecutionException e) { LOGGER.error("Error in Thread : {}", e); } catch (TimeoutException e) { LOGGER.error("Timeout in Thread : {}", e); } } ]]> Problem: Stalls indefinitely in case of stalled Callable(s) and consumes threads. Solution: Provide a timeout to the invokeAll/invokeAny method and handle the timeout. (jpinpoint-rules) 2 > executeTasksBad(Collection> tasks, ExecutorService executor) throws Exception { return executor.invokeAll(tasks); // bad, no timeout } private List> executeTasksGood(Collection> tasks, ExecutorService executor) throws Exception { return executor.invokeAll(tasks, OUR_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS); // good } } ]]> Problem: Stalls indefinitely in case of hanging threads and consumes a thread. Solution: Provide a timeout value and handle the timeout. (jpinpoint-rules) 1 complFuture) throws Exception { return complFuture.get(); // bad } public static String good(CompletableFuture complFuture) throws Exception { return complFuture.get(10, TimeUnit.SECONDS); // good } ]]> Problem: Stalls indefinitely in case of hanging thread(s) and consumes a thread. Solution: Provide a timeout before the join and handle the timeout. For example a future.get(timeout, unit), a orTimeout() or a completeOnTimeout(). You may want to use CompletableFuture.allOf() too. (jpinpoint-rules) 1 getOrdersBad(List> getOrdersFutures) { List orders = getOrdersFutures.stream() .map(CompletableFuture::join) // bad, NO timeout provided above .collect(Collectors.toList()); return orders; } private List getOrdersGood(List> getOrdersFutures) { // added to deal with timeout CompletableFuture allFuturesResult = CompletableFuture.allOf(getOrdersFutures.toArray(new CompletableFuture[getOrdersFutures.size()])); try { allFuturesResult.get(5L, TimeUnit.SECONDS); // good } catch (Exception e) { // should make explicit Exceptions //log error } List orders = getOrdersFutures.stream() .filter(future -> future.isDone() && !future.isCompletedExceptionally()) // keep only the ones completed - - added to deal with timeout .map(CompletableFuture::join) // good, timeout provided above .collect(Collectors.toList()); return orders; } ]]> A compound statement like i++, i- -, i += 1 or i -= 1 may seem one statement and thread-safe for a volatile field. Problem: The operation is actually two separate statements executed non-atomically and therefore not thread-safe. Solution: Guard the field properly with synchronized or use atomics like AtomicInteger. (jpinpoint-rules) 1 Problem: Mapped Diagnostic Context (MDC) of logging frameworks uses ThreadLocals to store things like traceIds from headers, userId, correlationId. Reactive programming uses multiple threads to handle a request, and one thread can deal with asynchronous steps of many requests. Therefore, MDC is tricky to use in reactive context and may take much processing time to propagate, likely so for much data in the MDC. Solution: Propagate by use of deferContextual and use directly from the Context only when and where needed. Avoid MDC propagation mechanisms. (jpinpoint-rules) 2 doIt(Map contextMap) { return Flux.fromIterable(service.doWork()) .doOnNext(s -> MDC.setContextMap(contextMap)) .doOnNext( response -> { log.info("your log"); // includes trace ids from MDC, put in a ServletFilter, specified by log configuration }); } } class FooGood { public Flux doIt() { // using deferContextual gives access to the read-only ContextView created in the ContextFilter return Flux.deferContextual(contextView -> service.doWork() .doOnNext(response -> { log.info("your log", StructuredArguments.entries((Map)contextView)); })); } } ]]> Problem: If a thread-unsafe collection is mutated in a parallelStream, this means that multiple threads access an unguarded shared variable which can cause data corruption and visibility problems: a concurrency bug. Solution: Using a thread-safe collection would fix the bug, however, the proper solution is to only use forEach for reporting/logging and let streams not have side effects. Moreover, parallelization is only faster in the exceptional case with large collections and much CPU work per element. Always measure if really faster.(jpinpoint-rules) 1 names = List.of("Dory", "Gill", "Bruce", "Nemo", "Darla", "Marlin", "Jacques"); List inUppercaseField = new ArrayList<>(); public static void main(String[] args) { List inUppercase = new ArrayList<>(); names.parallelStream().map(String::toUpperCase).forEach(name -> inUppercase.add(name)); // bad System.out.println(names.size()); // 7 System.out.println(inUppercase.size()); // sometimes 6! As Venkat's demo! } public void noBug() { List inUppercase = Collections.synchronizedList(new ArrayList<>()); // thread-safe collection names.parallelStream().map(String::toUpperCase).forEach(name -> inUppercase.add(name)); } public void good() { // pure functional form, proper solution. Note parallel may be faster, yet only with large collections. Always measure! List inUppercase = names.parallelStream().map(String::toUpperCase).toList(); // java 12+ } ]]> Problem: Multiple threads typically access static fields. Unguarded assignment to a mutable or non-final static field is thread-unsafe and may cause corruption or visibility problems. To make this thread-safe, that is, guard the field e.g. with synchronized methods, may cause contention. Solution: Make the fields final and unmodifiable. If they really need to be mutable, make access thread-safe: use synchronized and @GuardedBy, use volatile or a suitable concurrent collection type. Consider lock contention. (jpinpoint-rules) 3 0]) and not(./ModifierList/Annotation[@SimpleName='GuardedBy']) ] , (: static field, non-guarded, some often used known collection/array types, allocation side:) //FieldDeclaration[ pmd-java:modifiers() = ('static') and not(./ModifierList/Annotation[@SimpleName='GuardedBy']) and ( .//ConstructorCall/ClassType[ (pmd-java:typeIs('java.util.ArrayList') and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableList(_*)')]) ) or ((pmd-java:typeIs('java.util.HashMap') or pmd-java:typeIs('java.util.EnumMap')) and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableMap(_*)')])) or (pmd-java:typeIs('java.util.HashSet') and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableSet(_*)')])) ] or ./VariableDeclarator/ArrayAllocation/ArrayInitializer[@Length > 0] or ./VariableDeclarator/ArrayAllocation[.//ArrayDimExpr/NumericLiteral[@ValueAsInt > 0]] or ./VariableDeclarator/MethodCall[pmd-java:matchesSig('java.util.Arrays#asList(_*)') and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableList(_*)')])] or ./VariableDeclarator/MethodCall/TypeExpression[pmd-java:typeIs('java.util.EnumSet') and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableSet(_*)')])] ) ], (: static-block allocations of non-empty arrays :) //Initializer//ArrayAllocation[ArrayType/ArrayDimensions[@Size > 0]/ArrayDimExpr/NumericLiteral[@ValueAsInt > 0] or exists(.//ArrayDimExpr/VariableAccess/@Name) or exists(.//ArrayInitializer/*)] , (: static-block allocations of known mutable types :) //Initializer//ExpressionStatement//ConstructorCall[ ( (pmd-java:typeIs('java.util.ArrayList') and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableList(_*)')]) ) or ((pmd-java:typeIs('java.util.HashMap') or pmd-java:typeIs('java.util.EnumMap')) and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableMap(_*)')])) or (pmd-java:typeIs('java.util.HashSet') and not(ancestor::MethodCall[pmd-java:matchesSig('java.util.Collections#unmodifiableSet(_*)')])) ) and not(../VariableAccess/@Name = ancestor::ClassBody[1]/FieldDeclaration[count(ModifierList/Annotation[@SimpleName='GuardedBy']) > 0]/VariableDeclarator/VariableId/@Name) ] ]]> STRING_TO_ENUM = new HashMap<>(); // bad } enum Good { VAL1; static final Map STRING_TO_ENUM; // good static { Map map = new HashMap<>(); STRING_TO_ENUM = Collections.unmodifiableMap(map); } } ]]> Problem: Non-atomic If-Modify constructs are the most subtle and most occurring concurrency bugs. A ConcurrentMap is used in a multi-threading environment, and a separate if and modify is a concurrency bug. Solution: Use an atomic if-combined-with-modify operation provided by the ConcurrentMap, like putIfAbsent. (jpinpoint-rules) 1 accountMap, String accKey, String account) { if(!accountMap.containsKey(accKey)){ accountMap.put(accKey, account); } } void good(ConcurrentMap accountMap, String accKey, String account) { accountMap.putIfAbsent(accKey, account); } ]]> Problem: A singleton, or more general: an object shared among threads, has a field that is not private. These field can possibly be modified from other classes. Solution: Make the fields private. Note: fields are excluded when annotated with: @Autowired/@Value/@Inject. (jpinpoint-rules) 1 Problem: With Reactor Flux.parallel.runOn, the data is divided on a number of 'rails' matching the number of CPU cores. This is only useful in case much CPU processing is performed: if the sequential form takes more than 0,1 ms of CPU time. With remote calls this is usually not the case. In addition, it introduces more complexity with risk of failures. Solution: Remove parallel().runOn. Unless the CPU work takes more than 0,1 ms in sequential form and proves to be faster with parallelization. So only for large collections and much CPU processing. (jpinpoint-rules) 2 getResponseAccounts(List accountKeys, List requestedFields) { return Flux.fromIterable(accountKeys) .parallel(schedulerProperties.getParallelism()) //bad .runOn(scheduler) .flatMap(accountKey -> constructAccountDetails(accountKey, requestedFields)) .sequential(); } } class FooGood { public Flux getResponseAccounts(List accountKeys, List requestedFields) { return Flux.fromIterable(accountKeys) .flatMap(accountKey -> constructAccountDetails(accountKey, requestedFields)); } } ]]> Problem: Collection.parallelStream uses the common pool, with #threads = #CPUs. It is designed to distribute much CPU work over the cores. It is not designed for remote calls or other blocking calls. In addition, parallelizing has CPU overhead and concurrency risks. It should only be used for much pure CPU processing, e.g. on collections with > 10.000 elements. Solution: For remote/blocking calls: Use a dedicated thread pool with enough threads to get proper parallelism independent of the number of cores. For pure CPU processing: use ordinary sequential streaming unless the work takes more than about 0,1 ms in sequential form and proves to be faster with parallelization by measuring. So only for large collections and much processing without having to wait. (jpinpoint-rules) 2 map = new HashMap(); final List list = new ArrayList(); final List hugeList = new ArrayList(); //1000+ elements final ForkJoinPool myFjPool = new ForkJoinPool(10); final ExecutorService myExePool = Executors.newFixedThreadPool(10); void bad1() { list.parallelStream().forEach(elem -> someCall(elem)); // bad } void bad2() { map.entrySet().parallelStream().forEach(entry -> someCall(entry.getValue())); // bad } void exceptionalProperUse() { hugeList.parallelStream().forEach(elem -> heavyCalculations(elem)); // flagged but may be good, should suppress when proven to be faster than sequential form } void good1() { CompletableFuture[] futures = list.stream().map(elem -> CompletableFuture.supplyAsync(() -> someCall(elem), myExePool)) .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures).get(3, TimeUnit.SECONDS); } void good2() throws ExecutionException, InterruptedException { myFjPool.submit(() -> list.parallelStream().forEach(elem -> someCall(elem)) ).get(); } String someCall(String elem) { // do some call, don't know if remote or blocking. We don't use the returned value. return ""; } String heavyCalculations(String elem) { // calculate a lot return ""; } } ]]> Problem: Using Reactor Hooks.onEachOperator means executing the code on every operator in the Reactor flow, for every element. This typically means much processing time. Solution: Just do processing when and where actually needed. (jpinpoint-rules) 2 new MdcContextLifter<>(coreSubscriber)) // bad ); } } ]]> An XML Factory like DocumentBuilderFactory, TransformerFactory, MessageFactory is used as static field. Problem: These factory objects are typically not thread-safe and rather expensive to create because of class loading. Solution: Create the Factories as local variables and use command line arguments to specify the implementation class. (jpinpoint-rules) 1 Problem: JAXB Marshaller, Unmarshaller and Validator are not thread-safe. Solution: Create a new instance every time you need to marshall, unmarshall or validate a document. (jpinpoint-rules) 1 Problem: Multiple threads typically access fields of an object using synchronized. Unguarded assignment to a non-final field is thread-unsafe and may cause corruption or visibility problems. To make this thread-safe, that is, guard the field e.g. with synchronized methods, may cause contention. Solution: Make the fields final and unmodifiable. If they really need to be mutable, make access thread-safe: use synchronized and jcip @GuardedBy or use volatile. Notes: 1. In case you are sure the class is used in a single threaded context only, remove the current use of synchronized and annotate the class with @NotThreadSafe to make this explicit. 2. Use package-private and @VisibleForTesting for methods (e.g. setters) used for JUnit only. (jpinpoint-rules) 1 Problem: Multiple threads typically access fields of a singleton or may access fields in session scoped objects. Unguarded assignment to a non-final field is thread-unsafe and may cause corruption or visibility problems. It may also unintentionally mix-up session data. Solution: Make the fields final and unmodifiable. If they really need to be mutable, make access thread-safe: use synchronized and jcip @GuardedBy or use volatile. Notes 1. For autowiring/injection, the assignment of the reference is thread safe, so final is not required. The unmodifiable/thread-safe requirement for that field still holds. Also make sure no other thread-unsafe assignment is made to that field. 2. In case you are sure the Component is used in single threaded context only (e.g. a Tasklet), annotate the class with @NotThreadSafe to make this explicit. 3. Use package-private and @VisibleForTesting for methods (e.g. setters) used for JUnit only. 4. Use synchronized for accessors to inherited fields, or better: make field private and use proper accessors on base class level using @GuardedBy. (jpinpoint-rules) 1 Problem: Multiple threads typically access fields of an object using synchronized. If a field or its reference is mutable, access is thread-unsafe and may cause corruption or visibility problems. To make this thread-safe, that is, guard the field e.g. with synchronized methods, may cause contention. Solution: Make the fields final and unmodifiable. If they really need to be mutable, make access thread-safe: use synchronized and jcip @GuardedBy or use volatile. Notes: 1. Instances of Date, StringBuilder, URL and File are examples of mutable objects and should be avoided (or else guarded) as fields of shared objects. In case mutable fields are final and not modified after initialization (read-only) they are thread safe, however any modification to it is thread-unsafe. Since field modification is easily coded, avoid this situation. 2. Instances of classes like ArrayList, HashMap and HashSet are also mutable and should be properly wrapped with e.g. Collections.unmodifiableList after initialization (see TUTC03), or accessed thread-safely with e.g. Collections.synchronizedList or thread-safe implementations like ConcurrentHashMap. 3. For autowiring/injection, the assignment of the reference is thread safe, so final is not required. The unmodifiable/thread-safe requirement for that field still holds. Also make sure no other thread-unsafe assignment is made to that field. 4. In case you are sure the class is used in single threaded context only, annotate the class with @NotThreadSafe to make this explicit. 5. Use package private and @VisibleForTesting for methods used for JUnit only. (jpinpoint-rules) 3 0] ) ) (: or in-line allocation of known mutable collection types :) or ( ./VariableDeclarator/ConstructorCall/ClassType[ pmd-java:typeIs('java.util.ArrayList') or pmd-java:typeIs('java.util.HashMap') or pmd-java:typeIs('java.util.HashSet') ] ) (: or in-constructor allocation of known mutable collection types :) or ( ./VariableDeclarator/VariableId/@Name = ancestor::ClassDeclaration//ConstructorDeclaration//AssignmentExpression[ ConstructorCall/ClassType[ pmd-java:typeIs('java.util.ArrayList') or pmd-java:typeIs('java.util.HashMap') or pmd-java:typeIs('java.util.HashSet') ]]/VariableAccess/@Name ) ) (: -- GuardedBy annotations are matched on SimpleName, so it matches all known GuardedBy annotations :) (: -- net.jcip.annotations.GuardedBy, javax.annotation.concurrent.GuardedBy, com.google.errorprone.annotations.concurrent.GuardedBy :) (: mutable types not annotated with GuardedBy :) and not(ModifierList/Annotation[@SimpleName='GuardedBy']) ] ]]> Problem: Multiple threads typically access fields of a singleton or may access fields in session scoped objects. If a field or its reference is mutable, access is thread-unsafe and may cause corruption or visibility problems. It may also unintentionally mix-up session data. Solution: Make the fields final and unmodifiable. If they really need to be mutable, make access thread-safe: use synchronized and jcip `@GuardedBy` or use volatile. Notes: 1. Instances of Date, StringBuilder, URL and File are examples of mutable objects and should be avoided (or else guarded) as fields of shared objects. In case mutable fields are final and not modified after initialization (read-only) they are thread safe, however any modification to it is thread-unsafe. Since field modification is easily coded, avoid this situation. 2. Instances of classes like ArrayList, HashMap and HashSet are also mutable and should be properly wrapped with e.g. Collections.unmodifiableList after initialization (see TUTC03), or accessed thread-safely with e.g. Collections.synchronizedList or thread-safe implementations like ConcurrentHashMap. 3. For autowiring/injection, the assignment of the reference is thread safe, so final is not required. The unmodifiable/thread-safe requirement for that field still holds. Also make sure no other thread-unsafe assignment is made to that field. 4. In case you are sure the Component is used in single threaded context only (e.g. a Tasklet), annotate the class with @NotThreadSafe to make this explicit. 5. Use package private and @VisibleForTesting for methods used for JUnit only. (jpinpoint-rules) 1 0] ) (: or in-line allocation of known mutable collection types :) or ( ./VariableDeclarator/ConstructorCall/ClassType[ pmd-java:typeIs('java.util.ArrayList') or pmd-java:typeIs('java.util.HashMap') or pmd-java:typeIs('java.util.HashSet') ] ) (: or in-constructor allocation of known mutable collection types :) or ( ./VariableDeclarator/VariableId/@Name = ancestor::ClassDeclaration//ConstructorDeclaration//AssignmentExpression[ ConstructorCall/ClassType[ pmd-java:typeIs('java.util.ArrayList') or pmd-java:typeIs('java.util.HashMap') or pmd-java:typeIs('java.util.HashSet') ]]/VariableAccess/@Name ) ) (: not annotated GuardedBy :) and not (ModifierList/Annotation[@SimpleName='GuardedBy']) ] ]]> map1 = new HashMap<>(); // bad, reference and map is mutable private final Map map2 = new HashMap<>(); // bad, map is mutable private final Map map3 = new ConcurrentHashMap<>(); // good, concurrenthashmap is thread-safe @GuardedBy("this") private Map map4 = new HashMap<>(); // good, guarded private Map mapCtor1; // bad, reference and map is mutable private final Map mapCtor2; // bad, constructed with known mutable type private final Map mapCtor3; // good, concurrenthashmap is thread-safe @GuardedBy("this") private Map mapCtor4; // good, guarded public Bad() { mapCtor1 = new HashMap<>(); mapCtor2 = new HashMap<>(); mapCtor3 = new ConcurrentHashMap<>(); mapCtor4 = new HashMap<>(); } } @Service class BadMutableTypes { private final Date date = new Date(); // bad, Date is mutable private volatile StringBuilder strBuilder = new StringBuilder(); // bad, mutable type private final StringBuffer strBuffer = new StringBuffer(); // bad, mutable type private final String strOne = "abc"; // good, String is immutable private final String[] abc = new String[] {"a","b","c"}; // bad, elements can be replaced private final String[] nothing = new String[]{}; // good, no elements; not mutable } ]]> Problem: Multiple threads typically access fields of a singleton or may access fields in session scoped objects. If a (inherited) field or its reference is mutable, access is thread-unsafe and may cause corruption or visibility problems. It may also unintentionally mix-up session data. Solution: Make the fields final and unmodifiable. If they really need to be mutable, make access thread-safe: use synchronized and jcip @GuardedBy or use volatile. Notes: 1. Instances of Date, StringBuilder, URL and File are examples of mutable objects and should be avoided (or else guarded) as fields of shared objects. In case mutable fields are final and not modified after initialization (read-only) they are thread safe, however any modification to it is thread-unsafe. Since field modification is easily coded, avoid this situation. 2. Instances of classes like ArrayList, HashMap and HashSet are also mutable and should be properly wrapped with e.g. Collections.unmodifiableList after initialization (see TUTC03), or accessed thread-safely with e.g. Collections.synchronizedList or thread-safe implementations like ConcurrentHashMap. 3. For autowiring/injection, the assignment of the reference is thread safe, so final is not required. The unmodifiable/thread-safe requirement for that field still holds. Also make sure no other thread-unsafe assignment is made to that field. 4. In case you are sure the Component is used in single threaded context only (e.g. a Tasklet), annotate the class with @NotThreadSafe to make this explicit. 5. Use package private and @VisibleForTesting for methods used for JUnit only. (jpinpoint-rules) 1 The name of the field indicates user data. Problem: the field will be shared among users. If it is different data for each user, it can mix-up: a user may access data of another user, this is really bad. Solution: Do *not* put the user related data in a shared object e.g. by Spring @Component annotation. Use a POJO. Or, if it is not user data, rename the field. (jpinpoint-rules) 1 orderList; // bad private Headers headers; private List ordersRequiringAdditionalSignature; private String authUser; // bad private String executionDate; private String minimumLevel; private String sessionId; // bad private String payloadData; private String vmrUserId; // bad private String userref; // bad private String customerReference; // bad private String contract; // bad } @Component @Setter @Getter class VMROrderDetails { private static final String DESC_OF_ORDER = "order details"; // ok private final VMROrderDetails finalOrder = new VMROrderDetails(); // ok private OrderReference orderReference; // bad private Account originatorAccount; // bad private Amount amount; private String action; private List cancellationHistories; private String modifyType; private String order; // bad private String transactionRef; // bad } ]]> A field is defined as volatile while the class has prototype scope. Problem: volatile has some overhead, especially for writes. When getting the bean from the Spring applicationContext, prototype scope means that each invocation creates a new object so the field is not shared. Solution: Since only one thread can access the field, there is no need for volatile and it can be removed. (jpinpoint-rules) 2 Problem: The field to which the `@GuardedBy` annotation is applied is not accessed thread-safely as promised by GuardedBy. Solution: Make access thread-safe: synchronize access by a `synchronized(lock)` block with `lock` being a private final field. Alternatively, to avoid thread pinning with virtual threads before Java 24, use `ReentrantLock` with `try-finally`. Note that methods with annotations `@Autowired`, `@PostConstruct`, `@BeforeStep`, `@Value` and `@Inject` are ignored. (jpinpoint-rules) 2 Problem: The field to which this annotation is applied should only be accessed when holding the built-in 'this' lock by using synchronized. Solution: Make access thread-safe: synchronize access by method modifier or a synchronized(this) block. Note that methods with annotations @Autowired, @PostConstruct, @BeforeStep, @Value and @Inject are ignored. (jpinpoint-rules) 2 In the synchronized block, only local variables seem to be accessed. Problem: synchronization has overhead and may introduce lock contention. Solution: Remove synchronized because local variables are only accessible by the owning thread and are not shared. (jpinpoint-rules) 3 mapField; protected Map bad() { Map addHeaders = MDC.getCopyOfContextMap(); synchronized (this) { // bad if (addHeaders == null) { addHeaders = new HashMap<>(); } } return addHeaders; } protected Map good() { Map addHeaders = MDC.getCopyOfContextMap(); synchronized (this) { if (mapField == null) { mapField = new HashMap<>(); addHeaders = new HashMap<>(); } } return addHeaders; } } ]]> Problem: Wrong use of locking and '@GuardedBy' may result in thread-unsafety and concurrency bugs. It may obstruct other GuardedBy rules which check whether the promise of guarding is actually met, it may disguise high-risk violations. Solution: Use '@GuardedBy' properly: use a private final lock object constructed with 'new Object()' or 'new Object[0]'; a String literal of the lock name as '@GuardedBy' argument; and use that explicit or 'this' lock with 'synchronized'; or as ReentrantLock with try-finally, or '$lock' or '$LOCK' with lombok '@Synchronized'. (jpinpoint-rules) 2 cachedData = new HashMap<>(); // no guarding: no synchronized, promise not met, reported by another rule when GuardedBy is used correctly public static String getValue(String key) { return cachedData.get(key); } } class RightUseOfGuardedBy { private static final Object LOCK = new Object(); // proper lock object, static because the guarded field is static @GuardedBy("LOCK") // good, String literal referring to an existing, proper lock object private static final Map cachedData = new HashMap<>(); public static String getValue(String key) { synchronized(LOCK) { // guarding as promised by @GuardedBy return cachedData.get(key); } } } ]]> Problem: A JavaEE @Singleton has default @ConcurrencyManagement CONTAINER and write locks. Using defaults is unclear and write locks typically cause much more contention than read locks. Solution: Make @ConcurrencyManagement and @Lock-s explicit, @Lock on class level or on all public methods. Or use BEAN managed for full control using e.g. synchronized or volatile. (jpinpoint-rules) 3 0) ] , //ClassDeclaration[pmd-java:hasAnnotation('jakarta.ejb.Singleton')] (: @Singleton without @ConcurrencyManagement :) [not(pmd-java:hasAnnotation('jakarta.ejb.ConcurrencyManagement')) (: or @Singleton with @ConcurrencyManagement CONTAINER and not a class level @Lock or has public methods without @Lock :) or pmd-java:hasAnnotation('jakarta.ejb.ConcurrencyManagement') and .//Annotation[@SimpleName='ConcurrencyManagement']//(FieldAccess|VariableAccess)[@Name='CONTAINER'] and (not(pmd-java:hasAnnotation('jakarta.ejb.Lock'))) and (count(.//MethodDeclaration[@Visibility='public' and not(exists(.//Annotation[@SimpleName='Lock']))]) > 0) ] ]]> Problem: The org.springframework.boot.web.client.ClientHttpRequestFactorySupplier may return a HttpComponentsClientHttpRequestFactory which you supply as @Bean, however, this can silently go wrong and e.g. an unconfigured SimpleClientHttpRequestFactory can be returned. Default pool size and timeouts will be used, possibly resulting in very slow connection use. Solution: Provide your own supplier with explicit pool sizing and timeouts by a class implementing Supplier. (jpinpoint-rules) (jpinpoint-rules) 2 rt.getInterceptors() .add((request, body, execution) -> { request.getHeaders().add("SomeKey", someKey); return execution.execute(request, body); })) .requestFactory(new ClientHttpRequestFactorySupplier()) // bad .uriTemplateHandler(defaultUriBuilderFactory) .build(); return restTemplate; } } class MyClientHttpRequestFactorySupplier implements Supplier { public ClientHttpRequestFactory get() { PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(); poolingHttpClientConnectionManager.setDefaultMaxPerRoute(MAX_CONN_PER_ROUTE); poolingHttpClientConnectionManager.setMaxTotal(MAX_CONN_TOTAL); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(poolingHttpClientConnectionManager) .disableConnectionState() .build(); return new HttpComponentsClientHttpRequestFactory(httpClient); } } and use it to replace the bad line in Bad example: .requestFactory(new MyClientHttpRequestFactorySupplier()) // good ]]> Problem: Several HTTP client connection managers are thread-unsafe which may cause session data mix-up or have other issues for which they were made deprecated. Solutions: Upgrade to httpclient-4.5+ and use org.apache.http.impl.conn.PoolingHttpClientConnectionManager and e.g. org.apache.http.impl.client.HttpClientBuilder. (jpinpoint-rules) 2 Problem: Hystrix is not actively maintained anymore. Solution: Netflix recommends to use open source alternatives like resilience4j. (jpinpoint-rules) (jpinpoint-rules) 3 Problem: Apache HttpComponentsClientHttpRequestFactory has a constructor which takes a HttpClient and also a setter: setHttpClient. If you use both on the same factory, you discard all configuration done on the one provided in the constructor because it is replaced by the one provided to the setter. Solution: Don't use both on the same factory, provide the HttpClient only once to the factory. (jpinpoint-rules) (jpinpoint-rules) 2 0]]] /VariableId/@Name] ]]> Problem: SAAJ uses DOM to load the XML document in memory, which uses a TransformerFactory. The implementation class of it is loaded on every call that causes lock contention under load. This means long response times. Solution: If possible, use Axiom SOAP messaging which uses the faster StAX. If you have/want to stick to SAAJ, set the proper system properties to prevent the excessive class loading. Note: Axiom (2.0) is available again in Spring web services, since version 4.1. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: configuring connection settings like timeouts and pool sizes in code (int values) makes it difficult to manage and tune these settings. Solution: use property files, e.g. yml, to define the values for these settings for each called service. (jpinpoint-rules) (jpinpoint-rules) 3 Problem: the HttpHost constructor with one argument must only be provided with a host name, the default port 80 and protocol http are implied. The mistake of providing a URL and assuming it will be parsed into hostname, port and protocol is easily made. When this HttpHost is then used for a route and stored socketConfig for, port 80 is added for the host and the socketConfig is stored with the wrong key and will not be used. It is typically difficult to find out if the config is actually used. Solution: Use the HttpHost constructor with 2 (including port) or preferably 3 arguments (including port and protocol). Notes: 1. https://github.com/jborgers/http-client-monitor or 2. micrometer and apache http client 4 and 5 metrics with spring boot actuator, help here. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: JAXB utility methods do not reuse JAXBContext when more that one context is used. Solution: use JAXB API directly for marshalling and unmarshalling to gain all the performance benefits as described in IUOXAR04 and IUOXAR06. (jpinpoint-rules) 2 T myUnmarshal(final Source response, final Class clazz) { return JAXB.unmarshal(response, clazz); // bad } public void myMarshal(final Object response, StringWriter stringWriter) { JAXB.marshal(response, stringWriter); // bad } } ]]> Problem: A resilience4j retry event consumer is added to a retry event publisher for every method call. Likely a lambda retaining one or more objects. This will result in a growing list of consumers: a memory leak. Besides, the event will be sent to the growing number of consumers, taking more and more CPU time. Solution: Only call EventPublisher.onRetry (that is, add a consumer) in the same scope as the Retry instance lives. Note there is no way to unregister a consumer. (jpinpoint-rules) (jpinpoint-rules) 1 retryCountField.getAndIncrement()); // good } void callService() { AtomicInteger retryCountLocal = new AtomicInteger(); retryField.getEventPublisher().onRetry(event -> retryCountLocal.getAndIncrement()); // bad, lambda and AtomicInt leak Retry retryLocal = reg.retry("one per method call"); retryLocal.getEventPublisher().onRetry(event -> retryCountLocal.getAndIncrement()); // good, no leak // same for onSuccess, onError, onIgnoredError } } ]]> Problem: ObjectMapper is thread-safe only after configuration. Configuring an ObjectMapper is not thread-safe. Solution: Avoid configuring objectMappers except when initializing: right after construction. It is recommended to create ObjectReaders and ObjectWriters from ObjectMapper and pass those around since they are immutable and therefore thread-safe. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: Configuring an ObjectMapper is thread-unsafe. Solution: Create ObjectReaders and ObjectWriters from ObjectMapper and only share those as field, since they are immutable and therefore thread-safe. Exceptions: A convertValue method is not provided by ObjectReader nor ObjectWriter, therefore in those cases this rule is not applied. Also, when used like jaxMsgConverter.setObjectMapper(objectMapper) it is not considered a violation. And, when the class implements ContextResolver<ObjectMapper>. (jpinpoint-rules) (jpinpoint-rules) 3 :) [not(ancestor::ClassDeclaration/ImplementsList/ClassType[pmd-java:typeIs('javax.ws.rs.ext.ContextResolver')]/TypeArguments/ClassType[pmd-java:typeIs('com.fasterxml.jackson.databind.ObjectMapper')])] ]]> Problem: For troubleshooting Reactor, Blockhound can be used. It needs proper stack traces which can be achieved by Hooks.onOperatorDebug(). This can have much CPU overhead. Solution: Remove Hooks.onOperatorDebug() when not debugging. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: Apache HttpClient with its connection pool and timeouts should be setup once and then used for many requests. It is quite expensive to create and can only provide the benefits of pooling when reused in all requests for that connection. Solution: Create/build HttpClient with proper connection pooling and timeouts once, and then use it for requests. (jpinpoint-rules) (jpinpoint-rules) 2 connectBad(Object req) { HttpEntity requestEntity = new HttpEntity<>(req); HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(10).build(); // bad return remoteCall(httpClient, requestEntity); } } ]]> Problem: For Apache HttpRoute, if you don't specify whether the route is secure, the default of non-secure is taken. This is unclear. If you use it to configure a connection manager for that route, and the actual route is secure, the key does not match, and the intended configuration will not be effectuated. The default number of connections (2 for Http-Client version 4, and 5 for version 5) is used which may cause requests to wait long for a connection (without a proper timeout) resulting in bad responsiveness. Solution: Always specify in the constructor whether the route is secure (true) or not (false) to make it clear. Note: Covers Apache Http-Client 4 and 5. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: XMLGregorianCalendar is a large object, involving substantial processing. It is created with the poorly performing DatatypeFactory. Solution: Add a converter for alternative date handling with Java 8+ java.time. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: each Producer takes threads and memory. If you create it in each method call, and call this frequently, it will result in an explosion of threads and memory used and lead to Out Of Memory Error. Solution: Since the Axual Producer is thread-safe, it should be shared e.g. from a static field. (jpinpoint-rules) (jpinpoint-rules) 1 producer = axualClient.buildProducer(producerConfig); // bad producer.produce(msg); } } class AxualProducerGood1 { private static final Producer producer = AxualClient.buildProducer(producerConfig); } @Configuration class AxualProducerGood2 { public Producer axualProducer() { Producer producer = axualClient.buildProducer(producerConfig); return producer; } } ]]> org.springframework.http.client.BufferingClientHttpRequestFactory is used. Problem: It buffers all incoming and outgoing streams fully in memory, which may result in unnecessary high memory usage. Solution: Avoid when possible; avoid multiple reads of the response body so buffering is not needed. You may however still need it for Content-Length since Spring 6.1 (jpinpoint-rules) (jpinpoint-rules) 5 Problem: the default http client of Feign is java.net.HttpURLConnection that does not pool connections when using mutual TLS. This causes connection handshake overhead: extra CPU usage and higher latency. Solution: switch to a Feign client that supports HTTP connection pooling with mTLS, for instance Apache HttpClient 4 with disableConnectionState and proper connection pool size and timeouts. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: Gson creation is relatively expensive. A JMH benchmark shows a 24x improvement reusing one instance. Solution: Since Gson objects are thread-safe after creation, they can be between threads. So, reuse created instances, from a static field. Pay attention to use thread-safe (custom) adapters and serializers. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: If you use setConnectionManager, the connection pool must be configured on that Connection Manager. Pool settings on the client are ignored and lost. Solution: 1. HttpClients should either use setConnectionManager and *only* call setMaxTotal and setDefaultMaxPerRoute on that ConnectionManager or 2. not use a ConnectionManager and call setMaxConnTotal and setMaxConnPerRoute on the client directly (jpinpoint-rules) (jpinpoint-rules) 2 Problem: [For Apache HttpClient version 4 and 5] NTLM authenticated connections and SSL/TLS connections with client certificate authentication are stateful: they have a specific user identity/security context per session. If HttpClients have enabled connection state tracking, which is the default, established TLS connections will not be reused because it is assumed that the user identity or security context may differ. Then performance will suffer due to a full TLS handshake for each request. Solution: HttpClients should disable connection state tracking in order to reuse TLS connections, since service calls for one pool have the same user identity/security context for all sessions. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: when the [default max connections per route] external calls are being made to the same host, the next thread requesting a connection to that host must wait for a free connection. For Apache HttpClient v4, this default = 2 and for v5 this default = 5, often too low. Max total connections are 20 and 25 respectively. Solution: HttpClients should explicitly define the number of connections per route. If only one route is used, make the max number of connections equal to conn per route. Solution: 1. use setConnectionManager and call setMaxTotal and setDefaultMaxPerRoute on that connection manager, or 2. use no ConnectionManager, call setMaxConnTotal and setMaxConnPerRoute on the client directly (v4 only). (jpinpoint-rules) (jpinpoint-rules) 2 Problem: for connectionRequestTimeout, connectTimeout, socketTimeout (using HttpComponentsClientHttpRequestFactory/RequestConfig) or readTimeout/responseTimeout (using RequestConfig v4/v5) and connectTimeout (using ConnectionConfig v5) the default timeout settings are not optimal in most cases. Solution: Set the timeouts explicitly to proper reasoned values. See best practice values via the link. Use the setDefaultRequestConfig with a method with a RequestConfig object on HttpClient builders to set the timeouts. (jpinpoint-rules) (jpinpoint-rules) 2 org.apache.http.client.config.RequestConfig is used with connectionRequestTimeout and connectTimeout values above 500 milli seconds. Problem: 1. connectTimeout is for establishing a connection which should be quick, say below 200 ms. 2. connectionRequestTimeout is for requesting a connection from the connection manager, which should be almost as quick, say below 250 ms. If timeouts are long, requests will wait long for an unavailable service and cause high thread usage and possibly overload. Solution: Set connectTimeout and connectionRequestTimeout to values based om tests, for instance 200 ms and 250 ms. respectively (jpinpoint-rules) (jpinpoint-rules) 2 500] |.//Annotation[@SimpleName='Value']//StringLiteral[number(substring-before(substring-after(@Image, ':'), '}')) > 500] ) , (: use of literal, local var and return statement : :) //Block[ .//LocalVariableDeclaration/ClassType[ pmd-java:typeIs('org.apache.http.client.config.RequestConfig') or pmd-java:typeIs('org.springframework.http.client.HttpComponentsClientHttpRequestFactory') ] /..//VariableDeclarator |.//ReturnStatement[ .//MethodCall[@MethodName='custom']/TypeExpression/ClassType[ pmd-java:typeIs('org.apache.http.client.config.RequestConfig') or pmd-java:typeIs('org.springframework.http.client.HttpComponentsClientHttpRequestFactory') ] ] ] //MethodCall[@MethodName=('setConnectionRequestTimeout','setConnectTimeout')] /ArgumentList/NumericLiteral[@ValueAsLong > 500] ]]> Problem: If the interceptor throws an exception after receiving a response, resources are not released as happens with normal program flow. For instance, it may cause the connection not to be released to the connection pool, which leads to pool exhaustion and unresponsiveness. Solution: Release resources by closing the response via ClientHttpResponse.close() when throwing an Exception. (jpinpoint-rules) (jpinpoint-rules) 1 Problem: JAXBContext creation is expensive because it does much class loading. Solution: Since JAXBContext objects are thread safe, they can be shared between requests and reused. So, reuse created instances, e.g. as singletons. (jpinpoint-rules) 2 Problem: Jackson ObjectMapper/JsonMapper creation is expensive because it does much class loading. Solution: Since ObjectMapper/JsonMapper objects are thread-safe after configuration in one thread, they can be shared afterward between requests and reused. So, reuse created instances, from a static field. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: Multiple Retry locations in a call chain multiply the number of calls. For 2x retry on 3 locations (service calls) in a chain calling a system which is just recovering, results in 3 x 3 x 3 = 27 calls instead of 1. This may cause it not being able to restart. Solution: Have the retry mechanism in one location in the chain only, recommended only the one closest to the user. (jpinpoint-rules) (jpinpoint-rules) 5 The org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor queue capacity is not configured. Problem: It has a default queue capacity which is unlimited which can lead to an out of memory situation. Solution: Call setQueueCapacity, for instance with a value equal to CorePoolSize. Note that the pool will only grow beyond CorePoolSize up to MaxPoolSize when the queue is full. (jpinpoint-rules) (jpinpoint-rules) 2 Problem: if HttpClient connections are not closed properly when needed, resources are not released and connections may not (or not quick enough) become available from the pool. Solution: Use ClosableHttpClient to allow for invoking close on it to properly close the connection. Or use HttpComponentsClientHttpRequestFactory(httpClient) to let it manage closing. (jpinpoint-rules) (jpinpoint-rules) 2 Spring Expression Language (SpEL) expression is used for computing the key dynamically. Problem: evaluating the expression language is expensive, on every call. Solution: use a custom KeyGenerator: keyGenerator=... instead of key=... (jpinpoint-rules) 2 A non-overridden Object.toString may be called on a spring KeyGenerator.generate method parameter. Problem: The non-overridden Object.toString returns a String representing the identity of the object. Because this is different for two objects with the same value, cache keys will be different and the cache will only have misses and no hits. Solution: Cast the parameters each to the type used at call site and also check the expected number of params. Or better: return a SimpleKey composed typically of class and method name and the params. (jpinpoint-rules) 2 objArray = Arrays.asList(params); return target.getClass().getName() + "_" + method.getName() + "_" + StringUtils.arrayToDelimitedString(params, "_"); // bad, do not concatenate without casting } } public class Good implements KeyGenerator { public Object generate(Object target, Method method, Object... params) { if (params.length != 1) { throw new IllegalArgumentException("KeyGenerator for GetProfileCache assumes 1 parameter 'profileId', found: " + params); } String profileId = (String) params[0]; // good: includes cast return profileId; } } ]]> Improper combination of annotations. Problem: these annotations are not meant to be combined and may cause unexpected and unwanted behavior, e.g. data mix-up. Solution: remove the inappropriate annotation. Don't combine 2+ of [@Component, @Service, @Configuration, @Controller, @RestController, @Repository, @Entity] (Spring/JPA) Don't combine @Aspect with one of [@Service, @Configuration, @Controller, @RestController, @Repository, @Entity] (Spring/AspectJ) Don't combine [@Data with @Value] and [@Data or @Value] with any of [@ToString, @EqualsHashCode, @Getter, @Setter, @RequiredArgsConstructor] (Lombok) Don't combine @Data with any of [@Component, @Service, @Controller, @RestController, @Repository], it may cause user data mix-up. (jpinpoint-rules) 3 1 ]/Annotation[2] , //ClassDeclaration/ModifierList[ Annotation[pmd-java:typeIs('org.aspectj.lang.annotation.Aspect')] and count(Annotation[pmd-java:typeIs('org.springframework.stereotype.Controller') or pmd-java:typeIs('org.springframework.stereotype.Service') or pmd-java:typeIs('org.springframework.stereotype.Repository') or pmd-java:typeIs('org.springframework.context.annotation.Configuration') or pmd-java:typeIs('org.springframework.web.bind.annotation.RestController') or pmd-java:typeIs('javax.persistence.Entity') or pmd-java:typeIs('jakarta.persistence.Entity')]) > 0 ]/Annotation[2] , //ClassDeclaration/ModifierList[ count(Annotation[pmd-java:typeIs('lombok.Data') or pmd-java:typeIs('lombok.Value')]) > 1 ]/Annotation[2] , //ClassDeclaration/ModifierList[ Annotation[pmd-java:typeIs('lombok.Data') or pmd-java:typeIs('lombok.Value')] and Annotation[(pmd-java:typeIs('lombok.ToString') or pmd-java:typeIs('lombok.EqualsAndHashCode') or pmd-java:typeIs('lombok.Getter') or pmd-java:typeIs('lombok.Setter') or pmd-java:typeIs('lombok.RequiredArgsConstructor')) and not (AnnotationMemberList)] ]/Annotation[2] , //ClassDeclaration/ModifierList[ Annotation[pmd-java:typeIs('lombok.Data')] and Annotation[pmd-java:typeIs('org.springframework.stereotype.Controller') or pmd-java:typeIs('org.springframework.stereotype.Service') or pmd-java:typeIs('org.springframework.stereotype.Component') or pmd-java:typeIs('org.springframework.stereotype.Repository') or pmd-java:typeIs('org.springframework.web.bind.annotation.RestController')] ]/Annotation[not(pmd-java:typeIs('lombok.Data'))][1] ]]> Problem: ModelMaps are rather large objects containing explicitly added data and administrative data from Spring. They are added to the Portlet session implicitly. They stay in the session for some time: during session activity and 30 minutes (HTTP timeout) after it, in case the user does not exit explicitly. They occupy heap space during that time, for every user. Solution: Remove the ModelMap from the render method parameter list and create a new local ModelMap to use in the render request scope. (jpinpoint-rules) 2 Simple caches are used. Problem: Simple caching is meant for testing and prototyping plus it lacks manageability and monitorability. Solution: Use a proper cache implementation like ehcache or a cloud cache. (jpinpoint-rules) 2 Problem: Spring's SimpleKey creation lacks either the method or the method parameters, which may cause cache data mix-up. Solution: Create a SimpleKey composed of both the method object and the params Object[]. Make sure the params properly implement equals and hashCode. (jpinpoint-rules) 2 Problem: When a XXXApplicationContext is created, all Spring beans are initialized, wired and component scanning may take place. Component scanning involves extensive class path scanning which is expensive. Solution: Create the ApplicationContext only once in the application deployed/live time. (jpinpoint-rules) 2 Avoid to return an additive expression for a Spring Controller because it may cause a MemoryLeak. Each new value returned will create a new entry in the View Cache. Also avoid to return a ModelAndView object created using non-static and non-final methods because it may cause a MemoryLeak. Solution: Although multiple solutions exist you can make use of model attributes together with a redirectUrl, example: redirect:/redirectUrl?someAttribute={someAttribute}.(jpinpoint-rules) 1 Problem: When (1) concatenating or joining parameters in a KeyGenerator: they need to properly implement toString(). (2) using SimpleKey (recommended): the parameters need to properly implement equals() and hashCode(). Failing to do so may lead to caching data mix-up. Solution: Create a SimpleKey composed of both the method object and the params Object[] and make sure the params properly implement equals and hashCode. Note: This rule is just informational, because it cannot actually check if it is implemented correctly or not. (jpinpoint-rules) 5 Problem: Multiple threads typically access fields of a singleton or may access fields in session scoped objects. If a field or its reference is mutable, non-autowired access is thread-unsafe and may cause corruption or visibility problems. To make this thread-safe, that is, guard the field e.g. with synchronized methods, may cause contention. Solution: Make the fields final and unmodifiable to defend against mutation. If they really need to be mutable (which is strange for autowired fields), make access thread-safe. Thread-safety can be achieved e.g. by proper synchronization and use the @GuardedBy annotation or use of volatile. Notes 1. Autowiring/injection is thread safe, yet make sure no other thread-unsafe assignment is made to that field. 2. In case you are sure the Component is used in single threaded context only (e.g. a Tasklet), annotate the class with @NotThreadSafe to make this explicit. 3. Use package-private and @VisibleForTesting for methods (e.g. setters) used for JUnit only. (jpinpoint-rules) 3 A ModelMap is used in an action method typically for form validation and not cleared. Problem: the ModelMap is put in the session by Spring. This is typically a large object which may bloat the session. Solution: clear the ModelMap right after the validation in the happy flow. (jpinpoint-rules) 2 The cache by default allows multiple threads accessing by the same key. Problem: if the value of the key is not available from the cache, it will be fetched/computed by multiple threads while only one time is needed. Solution: Let only the first accessing thread fetch/compute the value and block others until the value is in the cache. Add attribute sync = "true" to achieve this. (Assuming the cache implementation supports it.) (jpinpoint-rules) 2 This class implementing Spring's KeyGenerator uses a generic name, CacheKeyGenerator or CacheKeyGeneration. Problem: It is unclear where this KeyGenerator should be used, for which cache and/or for which methods. If used on the wrong caches or methods, it may lead to cache key mix-up and user data mix-up. Solution: Make the name specific so that it is clear where to apply this KeyGenerator in @Cacheable. (jpinpoint-rules) 3 Problem: With default key generation, an object of Spring's SimpleKey class is used and its value is composed of just the method parameter(s). It does not include the method, which is unclear and risky. Solution: Create a KeyGenerator and make it generate a unique key for the cache per cached value, by use of SimpleKey composed of method object and the parameters. (jpinpoint-rules) 2 Problem: Unused rows are fetched and transported, and unused jdbc buffer is allocated. Solution: Use query.getSingleResult() in stead of query.getResultList(). (jpinpoint-rules) 2 query) { final List entities = query.getResultList(); return !entities.isEmpty() ? entities.get(0) : null; } public SomeEntity findByQueryGood(TypedQuery query) throws NoResultException { return query.getSingleResult(); } ]]> Problem: if huge numbers of result rows are fetched these are all stored in memory and this may introduce long gc times and out of memory risk. Solution: Set fetch size to 100 maximally. Only set it higher than 100 yet still max 500, if you are sure there is only little data returned per row, for instance 3 rather short columns. (jpinpoint-rules) 2 500] or VariableAccess/@Name = (ancestor::ClassBody[1]/FieldDeclaration|ancestor::MethodDeclaration)//VariableDeclarator[NumericLiteral[@ValueAsInt > 500]]/VariableId/@Name ] ]]> Problem: Time is taken by the unnecessary round trip(s). Unnecessary work is performed. Solution: Execute the query only once. (jpinpoint-rules) 2 1 ] ] ]]> Problem: The number of values for the IN-argument list is limited, in Oracle to 1000. An error occurs when exceeding this limit. Additionally, a large IN list takes much time to transport to the database and be parsed. Moreover, each number of IN values used in a query results in a separate cache entry in e.g. the Prepared Statement Cache of the application server and in the Hibernate Query Plan Cache, resulting in higher memory usage and/or low cache hit ratio. Solution: Rewrite the query by replacing the IN-argument list by a sub query using the criteria used to fetch the IN arguments. Or often even better performing, an inner join using these criteria (depending on indexes etc. - recommended to test to be sure.) This way, the select and update are combined into one, which will also save one roundtrip. (jpinpoint-rules) 2 cust) { cq.select(cust).where(cust.get("postalCode").in(codes)); // bad } private void good(CriteriaBuilder builder, CriteriaQuery cq, Root cust, SubQuery subquery) { cq.select(cust).where(builder.in(cust.get("postalCode")).value(subquery)); } ]]>