Skip to content

Support arbitrary levels of nested class names in ClassUtils.forName()#37021

Open
guanchengang wants to merge 1 commit into
spring-projects:mainfrom
guanchengang:fix-nested-class-resolution
Open

Support arbitrary levels of nested class names in ClassUtils.forName()#37021
guanchengang wants to merge 1 commit into
spring-projects:mainfrom
guanchengang:fix-nested-class-resolution

Conversation

@guanchengang

Copy link
Copy Markdown

Prior to this commit, ClassUtils#forName only performed a single-level conversion when resolving nested class names in Java source style (e.g. "java.lang.Thread.State"). For multi-level nested classes such as "com.example.Outer.Middle.Inner", the method would fail to resolve the actual class "com.example.Outer$Middle$Inner" because only the last dot was converted to a dollar sign.

This commit improves the resolution by introducing a loop that iteratively converts dots to dollar signs from right to left, until the class is found or no further conversion is possible. Additionally, if the given name already contains a dollar sign, the method now fails fast without attempting unnecessary conversions.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 9, 2026
Prior to this commit, ClassUtils#forName only performed a single-level
conversion when resolving nested class names in Java source style
(e.g. "java.lang.Thread.State"). For multi-level nested classes such as
"com.example.Outer.Middle.Inner", the method would fail to resolve the
actual class "com.example.Outer$Middle$Inner" because only the last dot
was converted to a dollar sign.

This commit improves the resolution by introducing a loop that iteratively
converts dots to dollar signs from right to left, until the class is found
or no further conversion is possible. Additionally, if the given name
already contains a dollar sign, the method now fails fast without
attempting unnecessary conversions.

Signed-off-by: Chengang Guan <guanchengang@qq.com>
@guanchengang guanchengang force-pushed the fix-nested-class-resolution branch from 6bcad67 to 4b0e0fa Compare July 9, 2026 08:04
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Jul 9, 2026
@sbrannen sbrannen changed the title Support recursive resolution for nested class names in Java source style Support arbitrary levels of nested class names in ClassUtils.forName() Jul 9, 2026
@guanchengang

Copy link
Copy Markdown
Author

Hi spring-team,
I'd like to open a design discussion on my PR.

The issue:

This modification caused CandidateComponentsIndexerTests#embeddedCandidatesAreDetected to fail in the spring-context-indexer module.

After investigation, I found the root cause: the test uses mixed-format nested class names like p.A.B$C – a format that contains both $ (JVM binary name style) and . (Java source style). The original implementation would convert the last dot to $, yielding p.A$B$C, and successfully load the class.

However, my PR added a fast-fail logic that rejects names already containing $ because they are technically not "Java source style" – they are JVM binary names.

The inconsistency in the original implementation:

Under the current implementation, class loading behaves inconsistently:

Input format Expected behavior Actual behavior
"p.A.B.C" (pure source style, 3-level nested) Should resolve to p.A$B$C ❌ Fails to load
"p.A$B.C" (mixed format) Should fail? (not a valid source style) ✅ Loads successfully
"p.A.B$C" (mixed format) Should fail? (not a valid source style) ✅ Loads successfully

p.A$B.C and p.A.B$C work only because the single-level conversion happens to convert the last dot to $, which coincidentally aligns with the actual binary name p.A$B$C after the second conversion. This is an accidental success, not an intentional design.

This is clearly inconsistent: if we support mixed formats, we should also support pure source-style multi-level names. If we don't, then mixed formats should not work either.

Questions for discussion:

Should mixed-format names like p.A$B.C be supported in ClassUtils.forName()?
If yes:

  • Multi-level pure source-style names (e.g., p.A.B.C) should also be supported for consistency.
  • The fast-fail logic should be removed.

If no:

  • Mixed-format names should not be supported.
  • Internal usages (including CandidateComponentsIndexerTests) should be updated to use either pure binary names or pure source-style names.

I'm happy to adapt the PR based on the team's decision. Please let me know your thoughts.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: core Issues in core modules (aop, beans, core, context, expression) status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants