Skip to content

[java] Fix #1102: improve consistency of utility class detection across rules#6691

Merged
UncleOwen merged 13 commits into
pmd:mainfrom
UncleOwen:issue-1102-Improve-consistency-of-utility-class-detection-across-rules
May 25, 2026
Merged

[java] Fix #1102: improve consistency of utility class detection across rules#6691
UncleOwen merged 13 commits into
pmd:mainfrom
UncleOwen:issue-1102-Improve-consistency-of-utility-class-detection-across-rules

Conversation

@UncleOwen

@UncleOwen UncleOwen commented May 17, 2026

Copy link
Copy Markdown
Member

Describe the PR

This PR standardizes what UseUtilityClassRule and JavaRuleUtil.isUtilityClass() (used by ClassNamingConventionsRule) mean by the term "utility class."

The new common definition is:

A class is a utility class, if and only if it fulfills ALL of the following criteria:

  • ALL member functions, member variables, nested classes, and initializers are static.
  • The class has at least one member function, member variable, or nested class that is not private.
  • The class is neither abstract nor an interface.
  • The class has no superclasses and implements no interfaces. (This might change in the future.)
  • The class has no main method.

Related issues

Ready?

  • Added unit tests for fixed bug/feature
  • Passing all unit tests
  • Complete build ./mvnw clean verify passes (checked automatically by github actions)
  • Added (in-code) documentation (if needed)

@pmd-actions-helper

pmd-actions-helper Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview

Compared to main:
This changeset changes 8 violations,
introduces 42 new violations, 0 new errors and 0 new configuration errors,
removes 73 violations, 0 errors and 0 configuration errors.
There are 0 changed duplications, 0 new duplications and 0 removed duplications.
There are 0 changed CPD errors, 0 new CPD errors and 0 removed CPD errors.

Regression Tester Report

(comment created at 2026-05-25 12:55:58+00:00 for 8fdaa02)

UncleOwen added 4 commits May 21, 2026 18:35
Changes in JavaRuleUtil.isUtilityClass()
* Add support for nested classes
* Abstract classes aren't utility classes
* Classes with main() method aren't utility classes
  This was broken before and handled inconsistently
Changes in UseUtilityClass
* Add support for initializers
* Classes with main() method aren't utility classes
* Remove special cases for JUnit3/4 suites
  - they are handled correctly by the current code.
@UncleOwen UncleOwen force-pushed the issue-1102-Improve-consistency-of-utility-class-detection-across-rules branch from cb51ad0 to 4b6e158 Compare May 21, 2026 17:07
@UncleOwen UncleOwen requested a review from zbynek May 24, 2026 16:52
@zbynek

zbynek commented May 24, 2026

Copy link
Copy Markdown
Contributor

The most significant user-facing change here is that classes that contain a main method are no longer considered utility classes -- maybe worth mentioning in release notes.

Checking https://github.com/pmd/java-regression-tests/blob/main/src/main/java/net/sourceforge/pmd/java/regression/tests/java21/Jep441_PatternMatchingForSwitch.java#L7 🤔

@UncleOwen

Copy link
Copy Markdown
Member Author

Good idea. And I just noticed that this doesn't have a release_notes entry, yet.

@UncleOwen

Copy link
Copy Markdown
Member Author

Hmm... but you're right. That is fishy... Does having a non-static nested class make the outer class stateful? It doesn't, right?

@UncleOwen

UncleOwen commented May 24, 2026

Copy link
Copy Markdown
Member Author

On the other hand: If we add a private, /throwing/ constructor to Jep441_PatternMatchingForSwitch, would we still be able to use those nested classes?

@zbynek

zbynek commented May 24, 2026

Copy link
Copy Markdown
Contributor

Nested interfaces, records and enums are always static, so the two lines you linked should not affect the utility status of the parent.

Nested classes that are not static should probably still make the parent non-utility class.
If the inner class is private, the static modifier could be suggested by a new rule similar to https://www.jetbrains.com/help/inspectopedia/InnerClassMayBeStatic.html#InnerClassMayBeStatic.topic

@UncleOwen

Copy link
Copy Markdown
Member Author

Nested interfaces, records and enums are always static, so the two lines you linked should not affect the utility status of the parent.

TIL. Thanks, that's definitely a TODO.

Nested classes that are not static should probably still make the parent non-utility class.

I think I agree, if only for consistency's sake. Oh, and because of JUnit Jupiters @Nested classes.

If the inner class is private, the static modifier could be suggested by a new rule similar to https://www.jetbrains.com/help/inspectopedia/InnerClassMayBeStatic.html#InnerClassMayBeStatic.topic

Do you want to write an issue for that, or should I?

@zbynek

zbynek commented May 24, 2026

Copy link
Copy Markdown
Contributor

Do you want to write an issue for that, or should I?

#6711

@adangel adangel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see, ClassNamingConvention already uses JavaRuleUtil.isUtilityClass, so nothing to change there.

Don't forget to update the rule descriptions of both rules to add the new definition for utility classes there:

  • For classes that only have static members, consider making them utility classes.
    Note that this doesn't apply to abstract classes, since their subclasses may
    well include non-static methods. Also, if you want this class to be a utility class,
    remember to add a private constructor to prevent instantiation.
    (Note, that this use was known before PMD 5.1.0 as UseSingleton).
  • For this rule, a utility class is defined as: a concrete class that does not
    inherit from a super class or implement any interface and only has static fields
    or methods.

Out of scope for this change, but maybe something to consider in the future: I don't think the name of the rule "UseUtilityClass" is a good name... It's more like: "Hey, this class looks like a utility class, but it still can be instantiated although it doesn't have state. This doesn't make sense. Consider to add a private constructor ..." The rule is named better maybe "InstantiableUtilityClass"?

Comment thread docs/pages/release_notes.md Outdated
…rnal/JavaRuleUtil.java

Co-authored-by: Andreas Dangel <andreas.dangel@adangel.org>

@zbynek zbynek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As discussed, interfaces, records and enums should not be considered for utility class check, otherwise looks good.

@UncleOwen

UncleOwen commented May 25, 2026

Copy link
Copy Markdown
Member Author

Updated rule descriptions. Nested interfaces, records and enums are static, those no longer prevent a class from being considered a utility class.

Could someone please have a look at this test case? Why do I get a parse error? javac (21) compiles this code without issue. Is this a bug in our parser?

Comment thread pmd-java/src/main/resources/category/java/design.xml Outdated
UncleOwen and others added 2 commits May 25, 2026 13:55
Co-authored-by: Andreas Dangel <andreas.dangel@adangel.org>

@adangel adangel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, looks good!

@UncleOwen

Copy link
Copy Markdown
Member Author

Thanks for the reviews!

@UncleOwen UncleOwen merged commit e0e604a into pmd:main May 25, 2026
13 checks passed
@UncleOwen UncleOwen deleted the issue-1102-Improve-consistency-of-utility-class-detection-across-rules branch May 25, 2026 15:25
@UncleOwen UncleOwen added this to the 7.25.0 milestone May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[java] Improve consistency of utility class detection across rules

3 participants