Skip to content

PMD 7.28.0 (25-September-2026)

Choose a tag to compare

@pmd-actions-helper pmd-actions-helper released this 25 Sep 09:00
· 96 commits to main since this release
pmd_releases/7.28.0
79726f0

25-September-2026 - 7.28.0

The PMD team is pleased to announce PMD 7.28.0.

This is a minor release.

Table Of Contents

🚀️ New and noteworthy

Kotlin XPath functions and type attributes

Type data is now accessible in XPath rules via new attributes and helper functions (see Kotlin XPath rule support):

  • Type-info Attributes: @TypeName, @ReturnTypeName, @AnnotationFqNames
    are exposed on declaration nodes (property, function, class, parameter, catch, for-loop, delegation specifier,
    annotation nodes). These attributes depend on type resolution: they are only available when auxClasspath
    is configured and the kotlin-type-mapper analysis has resolved the types.
  • General Attributes: @Mutable, @Identifier, @Name
    are exposed on declaration and import related nodes. These attributes don't depend on type resolution, so
    they're always present regardless of auxClasspath.
  • XPath functions:
    • pmd-kotlin:typeIs(typeName): matches if the node's type is typeName or a subtype.
    • pmd-kotlin:typeIsExactly(typeName): matches the exact declared type only (no subtypes).
    • pmd-kotlin:hasAnnotation(name): matches if the node has an annotation with the given simple or FQN.
    • pmd-kotlin:modifiers(): returns the modifier keywords of a declaration as a sequence.
    • pmd-kotlin:isNullable(): returns true if the node's declared type is nullable (has ?).
    • pmd-kotlin:hasUnresolvedReference(): returns true if the node contains an unresolved reference.
    • pmd-kotlin:matchesSig(signature): matches call sites by method signature pattern (supports wildcards).

🌟️ New and Changed Rules

New Rules

  • The new Java rule OnDemandImport reports on-demand imports, also known as wildcard imports.
    By default, static imports from JUnit and TestNG are allowed. The allowed static and type import packages
    can be configured with allowStaticImportsFrom and allowTypeImportsFrom.
  • The new Java rule LongLiteralEndingWithLowercaseL finds long literals ending with a lowercase l.
    That helps to avoid confusion between numbers ending with 1 and l. Uppercase L should be used to define long literals.
  • The new Java rule TypeNameMismatch finds types that are not defined in a .java file
    with the same name. Enforcing a match between source file name and type name makes it easier to
    find source code for given type.
  • The new Java rule CStyleArrayDeclaration finds C-style declarations of arrays (e.g. int numbers[]).
    That helps you use Java-style declarations (e.g. int[] numbers) consistently throughout the codebase.
  • The new Java rule InternalApiUsage reports usages of internal or test-only APIs
    (e.g. annotated with @VisibleForTesting, @TestOnly, @API(status=INTERNAL) or @ApiStatus.Internal)
    from code that shouldn't depend on them.
  • The new Apex rule ApexUnitTestClassShouldHaveRunRelevantTestsAnnotation finds unit tests
    that do not use the new @IsTest(critical=true) or @IsTest(testFor='...') annotation parameters for tests.
    These parameters help to identify which tests should be executed during a RunRelevantTests deployment.
    Note: These annotation parameters are Beta and require Salesforce API 66.0+.

Changed Rules

  • The property checkNonStaticMethods of the rule NonThreadSafeSingleton is now
    deprecated and no longer has any effect. Its implementation did the opposite of what the documentation described.
    The rule now always reports both static and non-static methods; previously it reported only static methods
    by default.
    This may result in additional violations being reported.
    If you want to suppress violations for non-static methods, you can use
    suppression via XPath, e.g.
    <property name="violationSuppressXPath" value=".[ancestor-or-self::MethodDeclaration[1][@Static = false()]]" />
  • The property statementOrderMatters of the rule VariableCanBeInlined is now deprecated.
    Setting it to false relaxes the rule under the unsafe assumption that intervening statements have no side
    effects, which can lead to false positives. The property will be removed in PMD 8.0.0.

🐛️ Fixed Issues

  • apex-bestpractices
    • #6988: [apex] New rule: Detect usage of @IsTest(critical=true) / @IsTest(testFor='...') annotations (RunRelevantTests, Beta, API 66.0+)
  • core
    • #7013: [core] PMDConfiguration - "Can't mix setClasspath with getAuxClasspath!"
  • cli
    • #7090: [cli] Add the missing exit code 5 to the CLI help
  • html
    • #6135: [html] HtmlCpdLexer giving IndexOutOfBoundsException when script contains unescaped closing tag
  • java
    • #6926: [java] IllegalArgumentException (Mismatched list sizes) with inconsistent unresolved generic arity
    • #7056: [java] Provide ability to disable auxClasspath warning added in 7.27.0
    • #7081: [java] NoSuchFileException when auxClasspath is given as a classpath file (file: URL) (since 7.27.0)
    • #7101: [java] ZipException when auxClasspath contains a non-jar file (since 7.27.0)
  • java-bestpractices
    • #5940: [java] UnusedAssignment: False positive when assignment is in conditional statement
    • #6901: [java] MethodReturnsInternalArray: Various false negatives with local aliases and conditional expressions
    • #7033: [java] New rule: TypeNameMismatch
    • #7047: [java] New rule: OnDemandImport
  • java-codestyle
    • #3124: [java] UnnecessaryLocalBeforeReturn/VariableCanBeInlined: deprecate property statementOrderMatters
    • #5732: [java] UnnecessaryCast false positive with package private methods
    • #7026: [java] New rule: CStyleArrayDeclaration
  • java-design
    • #6513: [java] SimplifyConditional: False negative when null check and instanceof are separated by other && conditions
    • #6694: [java] SimplifyBooleanReturns triggers inconsistently depending on redundant parentheses in return expression
    • #6889: [java] New rule: InternalApiUsage
  • java-documentation
    • #6450: [java] DanglingJavadoc: False positive on /// comments for Java < 23
  • java-errorprone
    • #1050: [java] NullAssignment: False positive inside if statement for first assignment
    • #6693: [java] CloneMethodMustImplementCloneable: False positive with throw-via-local
    • #7009: [java] ReplaceJavaUtilDate: False negative when using pattern matching
    • #7027: [java] New rule: LongLiteralEndingWithLowercaseL
    • #7068: [java] UnusedReturnValue: False positive for calls made on Mockito.verify(mock)
  • java-multithreading
    • #6297: [java] AvoidUsingVolatile: Update documentation
    • #6780: [java] NonThreadSafeSingleton: False negative with property checkNonStaticMethods
  • java-security
    • #7007: [java] HardCodedCryptoKey: False negative when a hard-coded key is constructed via new String(char[])
    • #7008: [java] HardCodedCryptoKey: False positive when the key comes from System.getProperty()
  • kotlin
    • #6893: [kotlin] Add XPath functions and type attributes
  • miscellaneous
    • #6961: [doc] When a rule's description has a link to another rule in the exact wrong position, the doc generation crashes

🚨️ API Changes

Experimental API

✨️ Merged pull requests

📦️ Dependency updates

  • #5421: chore(deps): bump me.tongfei:progressbar from 0.9.5 to 0.10.2
  • #7004: Bump PMD from 7.26.0 to 7.27.0
  • #7021: chore(deps): bump org.apache.groovy:groovy from 5.1.0 to 5.1.1
  • #7022: chore(deps): bump crate-ci/typos from 1.49.0 to 1.50.0
  • #7023: chore(deps): bump actions/setup-java from 5.7.0 to 6.0.0
  • #7024: chore(deps): bump org.jsoup:jsoup from 1.23.1 to 1.23.2
  • #7037: chore(deps-dev): bump byte.buddy.version from 1.18.12 to 1.18.13
  • #7038: chore(deps-dev): bump org.projectlombok:lombok from 1.18.46 to 1.18.48
  • #7039: chore(deps-dev): bump ant.version from 1.10.17 to 1.10.18
  • #7040: chore(deps): bump surefire.version from 3.5.6 to 3.6.0
  • #7041: chore(deps): bump org.yaml:snakeyaml from 2.6 to 2.7
  • #7042: chore(deps-dev): bump io.github.git-commit-id:git-commit-id-maven-plugin from 10.0.0 to 10.0.1
  • #7043: chore(deps): bump com.puppycrawl.tools:checkstyle from 14.0.0 to 14.1.0
  • #7044: chore(deps): bump com.google.protobuf:protobuf-java from 4.36.0 to 4.36.1
  • #7045: chore(deps): bump org.apache.maven.plugins:maven-compiler-plugin from 3.15.0 to 3.16.0
  • #7046: chore(deps): bump com.github.siom79.japicmp:japicmp-maven-plugin from 0.26.1 to 0.26.2
  • #7049: chore(deps): bump crate-ci/typos from 1.50.0 to 1.50.1
  • #7051: chore(deps): bump org.apache.groovy:groovy from 5.1.1 to 5.1.2
  • #7058: chore(deps-dev): bump org.sonarsource.scanner.maven:sonar-maven-plugin from 5.7.0.6970 to 5.8.0.7211
  • #7074: chore(deps-dev): bump org.codehaus.mojo:versions-maven-plugin from 2.21.0 to 2.22.0
  • #7075: chore(deps-dev): bump org.apache.maven.plugins:maven-install-plugin from 3.1.4 to 3.2.0
  • #7076: chore(deps): bump scalameta.version from 4.17.3 to 4.17.4
  • #7077: chore(deps): bump org.codehaus.mojo:exec-maven-plugin from 3.6.3 to 3.6.4
  • #7078: chore(deps): bump org.codehaus.mojo:extra-enforcer-rules from 1.12.0 to 1.12.1
  • #7079: chore(deps-dev): bump org.apache.maven.plugins:maven-deploy-plugin from 3.1.4 to 3.2.0
  • #7080: chore(deps): bump org.codehaus.mojo:build-helper-maven-plugin from 3.6.1 to 3.6.2
  • #7082: chore(deps): bump actions/setup-java from 6.0.0 to 6.0.1
  • #7096: chore(deps-dev): bump byte.buddy.version from 1.18.13 to 1.18.14
  • #7095: chore(deps): bump crate-ci/typos from 1.50.1 to 1.50.2
  • #7097: chore(deps): bump bigdecimal from 4.1.2 to 4.1.3 in /docs
  • #7098: chore(deps): bump ruby/setup-ruby from 1.321.0 to 1.324.0

📈️ Stats

  • 74 commits
  • 69 closed tickets & PRs
  • Days since last release: 27