Skip to content

Conversation

@slachiewicz
Copy link
Member

Problem

The excludePackageNames parameter with trailing wildcard patterns like org.example.* was only matching direct subpackages (one level deep), but not nested subpackages. This caused builds to fail when using wildcards to exclude package hierarchies, particularly noticeable with Java 25.

For example, with <excludePackageNames>org.example.*</excludePackageNames>:

  • ✅ Correctly excluded: org.example.sub1, org.example.sub2
  • Not excluded: org.example.sub2.subsub, org.example.sub1.deep.nested

This was inconsistent with the javadoc -exclude option behavior, which according to Oracle's documentation "unconditionally excludes the specified packages and their subpackages."

Root Cause

The regex pattern conversion in JavadocUtil.getExcludedPackages() was treating all non-leading wildcards the same way, converting * to [^/]+ which matches exactly one directory level. This meant:

  • org.example.*org/example/[^/]+ (matches only one level)
  • Should be: org.example.*org/example/.+ (matches one or more levels)

Solution

Updated the wildcard-to-regex conversion logic to distinguish between:

  1. Leading wildcards (*.internal) → .+ - matches one or more package segments
  2. Trailing wildcards (org.example.*) → .+ - matches one or more package segments (all subpackages)
  3. Middle wildcards (org.*.sub1) → [^/]+ - matches exactly one package segment

This ensures trailing wildcards correctly exclude all subpackages at any depth, aligning with javadoc's -exclude option behavior.

Changes

  • JavadocUtil.java: Fixed regex pattern generation to handle trailing wildcards with .+ instead of [^/]+
  • AbstractJavadocMojo.java: Updated documentation with clearer examples showing wildcard behavior
  • JavadocUtilTest.java: Added comprehensive test coverage for wildcard patterns including nested subpackages

Example

With this fix, <excludePackageNames>org.example.*</excludePackageNames> now correctly excludes:

  • org.example.sub1
  • org.example.sub2
  • org.example.sub2.subsub ✨ (previously missed)
  • org.example.sub1.deep.nested ✨ (previously missed)

Fixes issue where excludePackageNames with wildcards stopped working correctly in recent Java versions.

@slachiewicz slachiewicz added bug Something isn't working hacktoberfest-accepted labels Oct 10, 2025
@slachiewicz slachiewicz requested a review from Copilot October 15, 2025 22:40
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Fixes excludePackageNames wildcard handling so trailing wildcards match all nested subpackages, aligning behavior with javadoc’s -exclude semantics for recursive exclusion.

  • Update regex generation to treat leading and trailing wildcards as recursive (.+) and middle wildcards as single-segment ([^sep]+)
  • Clarify plugin Javadoc to document wildcard behavior with concrete examples
  • Add tests covering recursive and single-segment wildcard scenarios

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java Adjust wildcard-to-regex conversion to correctly handle trailing wildcards as recursive.
src/main/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojo.java Update documentation and examples to reflect new wildcard behavior.
src/test/java/org/apache/maven/plugins/javadoc/JavadocUtilTest.java Add tests validating recursive and single-level wildcard matching across various patterns.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +392 to +395
// Following implementation should match requirements as aligned with javadoc -exclude behavior:
// - A wildcard at the beginning should match one or more directories
// - Any other wildcard must match exactly one directory
Pattern p = Pattern.compile(excludePackagename
.replace(".", regexFileSeparator)
.replaceFirst("^\\*", ".+")
.replace("*", "[^" + regexFileSeparator + "]+"));
// - A wildcard at the end should match one or more directories (to include all subpackages)
// - A wildcard in the middle should match exactly one directory
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

The comment suggests full alignment with javadoc -exclude behavior, but the plugin’s documented behavior (and tests) keep bare package names (e.g., org.acme.exclude2) as exact-only (not recursive). Please rephrase to clarify this only aligns trailing wildcards with javadoc, not bare package names.

Copilot uses AI. Check for mistakes.
Comment on lines +396 to +402
String pattern = excludePackagename.replace(".", regexFileSeparator);
// Handle leading wildcard: match one or more directory levels
pattern = pattern.replaceFirst("^\\*", ".+");
// Handle trailing wildcard: match one or more directory levels (for subpackages)
pattern = pattern.replaceFirst("\\*$", ".+");
// Handle wildcards in the middle: match exactly one directory level
pattern = pattern.replace("*", "[^" + regexFileSeparator + "]+");
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

[nitpick] To avoid unintended regex interpretation of literal characters, build the regex by quoting non-wildcard fragments and inserting the appropriate wildcard expansions based on position. This keeps behavior the same while making the transformation robust against any non-package characters.

Suggested change
String pattern = excludePackagename.replace(".", regexFileSeparator);
// Handle leading wildcard: match one or more directory levels
pattern = pattern.replaceFirst("^\\*", ".+");
// Handle trailing wildcard: match one or more directory levels (for subpackages)
pattern = pattern.replaceFirst("\\*$", ".+");
// Handle wildcards in the middle: match exactly one directory level
pattern = pattern.replace("*", "[^" + regexFileSeparator + "]+");
// Build regex by quoting non-wildcard fragments and inserting wildcard expansions
StringBuilder patternBuilder = new StringBuilder();
String[] parts = excludePackagename.split("\\*", -1); // -1 to include trailing empty strings
for (int i = 0; i < parts.length; i++) {
if (!parts[i].isEmpty()) {
// Replace '.' with file separator and quote the fragment
patternBuilder.append(Pattern.quote(parts[i].replace(".", regexFileSeparator)));
}
if (i < parts.length - 1) {
// Insert wildcard regex
// If at start or end, use ".+" (one or more directories), else "[^sep]+"
boolean isStart = (i == 0);
boolean isEnd = (i == parts.length - 2 && parts[parts.length - 1].isEmpty());
if (isStart || isEnd) {
patternBuilder.append(".+");
} else {
patternBuilder.append("[^").append(regexFileSeparator).append("]+");
}
}
}
String pattern = patternBuilder.toString();

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working hacktoberfest-accepted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant