Skip to content

Modernize codebase with Java improvements #2277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

gnodet
Copy link
Contributor

@gnodet gnodet commented Apr 25, 2025

This PR includes several improvements to modernize the codebase:

  1. Replace IllegalArgumentException with ClassCastException for type cast failures
  2. Replace IllegalArgumentException with NullPointerException for null checks
  3. Use modern Java collections API (toList() instead of collect(Collectors.toList()))
  4. Replace custom null checks with Objects.requireNonNull
  5. Add @nonnull and @nullable annotations throughout the codebase

These changes make the code more maintainable, safer, and aligned with modern Java practices.

@kwin
Copy link
Member

kwin commented Apr 27, 2025

Regarding point 2. (and the related point 4.): I consider the IAE more concise than an NPE in case a method argument is passed with null (although not allowed). Compare also with https://discuss.kotlinlang.org/t/why-does-requirenotnull-throw-an-illegalargumentexception/7617/4

@desruisseaux
Copy link
Contributor

I think that the main argument in favour of NullPointerException is consistency. Not all methods perform explicit null checks. Especially since JEP 358: Helpful NullPointerExceptions provides even more helpful messages than many explicit null checks. Therefore, if Objects.requireNonNull was throwing IllegalArgumentException for a null argument, we would be at risk of having sometime an IllegalArgumentException, and sometime a NullPointerException, depending on whether the method chooses to perform explicit null checks or not. It would also be a risk of compatibility break if the method implementation changes its strategy over time.

@gnodet gnodet force-pushed the refactoring branch 2 times, most recently from c52c814 to b071032 Compare May 1, 2025 05:56
@Pankraz76
Copy link
Contributor

Pankraz76 commented May 1, 2025

  • Use modern Java collections API (toList() instead of collect(Collectors.toList()))

Hi folks, please check out attached openrewrite link as its related and some can be done by computer.

This could be supplemented in dedicated PR as there is a migration for this:

Maybe we can team up with as its kind of related. The migration is "free" as done by one click so we can reproduce anytime.

@Pankraz76
Copy link
Contributor

@Pankraz76
Copy link
Contributor

its nice to fix this once, but without a computer checking and enforcing this by some tool like checkstyle or rewrite, its not completed.

@Pankraz76
Copy link
Contributor

this is wrong. Only the upgradetojava21 recipe does the desired migration. Anyways the PR is now fixed. #2287

@Pankraz76
Copy link
Contributor

Pankraz76 commented May 2, 2025

@gnodet
Copy link
Contributor Author

gnodet commented May 2, 2025

its nice to fix this once, but without a computer checking and enforcing this by some tool like checkstyle or rewrite, its not completed.

I don't see where it's done in your PR as it only provides the result of executing the transformation.

@@ -337,7 +335,7 @@ public LocalRepository getLocalRepository() {
@Nonnull
@Override
public Session withLocalRepository(@Nonnull LocalRepository localRepository) {
nonNull(localRepository, "localRepository");
Objects.requireNonNull(localRepository, "localRepository cannot be null");
Copy link
Contributor

@Pankraz76 Pankraz76 May 2, 2025

Choose a reason for hiding this comment

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

Current State of NullPointerException Handling

The current implementation around NullPointerException is not ideal, but in context, it's clear what's happening.

Options:

  1. Keep it as-is
    • Works but lacks elegance.
  2. Remove all such checks
    • Simplifies but loses explicit null-check messaging.
  3. Refactor into a dedicated layer
    • Separate the "cannot be null" concern to avoid:
      • DRY violations (repetition)
      • Feature envy (fluff and copy-paste)

Proposal: Dedicated Logging Layer

If improved logging is desired, this concern should be moved to a dedicated utility layer, similar to Apache Maven's approach:

throw new IllegalArgumentException(name + " cannot be null");

Benefits:

  • Contextual clarity: Right now, the check is just a short "non-null" hint.
  • Better debugging: Explicit messaging improves NullPointerException readability.
  • Consistency: Centralized handling avoids duplication.

This change is already reflected in PR #2290.


@@ -134,7 +132,7 @@ public AbstractSession(
List<RemoteRepository> repositories,
List<org.eclipse.aether.repository.RemoteRepository> resolverRepositories,
Lookup lookup) {
this.session = nonNull(session, "session");
Copy link
Contributor

@Pankraz76 Pankraz76 May 2, 2025

Choose a reason for hiding this comment

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

Current State of NullPointerException Handling

The current implementation around NullPointerException is not ideal, but in context, it's clear what's happening.

Options:

  1. Keep it as-is
    • Works but lacks elegance.
  2. Remove all such checks
    • Simplifies but loses explicit null-check messaging.
  3. Refactor into a dedicated layer
    • Separate the "cannot be null" concern to avoid:
      • DRY violations (repetition)
      • Feature envy (fluff and copy-paste)

Proposal: Dedicated Logging Layer

If improved logging is desired, this concern should be moved to a dedicated utility layer, similar to Apache Maven's approach:

throw new IllegalArgumentException(name + " cannot be null");

Benefits:

  • Contextual clarity: Right now, the check is just a short "non-null" hint.
  • Better debugging: Explicit messaging improves NullPointerException readability.
  • Consistency: Centralized handling avoids duplication.

This change is already reflected in PR #2290.


@gnodet
Copy link
Contributor Author

gnodet commented May 3, 2025

@Pankraz76 I'm not sure what you aim for with your PRs. How do they differ from this one ? It's fine very fine that you used openrewrite rules, but isn't the result the same at the end ?

@Pankraz76
Copy link
Contributor

@Pankraz76 I'm not sure what you aim for with your PRs. How do they differ from this one ? It's fine very fine that you used openrewrite rules, but isn't the result the same at the end ?

Hello friend, yes thats the goal see reasoning below:

Refactoring Large PRs: The Case for Atomic Changes/Why Smaller Changes Win

Key Insight:
When executed correctly, changes maintain a strict one-to-one (1:1) correspondence—preserving merge integrity. A targeted rebase could reduce the diff from ~200 files to ~100, dramatically enhancing review efficiency.

Why Monolithic PRs Fail:
We systematically reject PRs with hundreds of modified files because they:

  • 🚫 Defy human review (cognitive overload)
  • 🤖 Require blind automation (whereas small PRs enable thoughtful review)
  • 💥 Create binary outcomes (either perfect or disastrous - no middle ground)

This PR's Structural Flaw:
While combining refactoring + modernization, it suffers from:

  • 🌀 Concern entanglement (multiple domains modified simultaneously)
  • 🎲 Gambler's fallacy ("all changes must be perfect")
  • 🏚️ Feature envy (components overstepping their boundaries)

The Atomic PR Advantage:
Strategic splitting delivers:
Reviewer ergonomics (focused attention beats fatigue)
Precision rollbacks (targeted fixes without collateral damage)
Architectural purity (SoC + SRP enforced naturally)
Feature envy prevention (clear ownership boundaries)

The Granularity Paradox:
While defining "one logical change" is subjective, we resolve this through:

  • 🧱 Strict module boundaries
  • ⚖️ Cost/benefit weighing (if unsure, split smaller)

"These patterns persist because they survive Darwinian selection in production systems."

Copy link
Contributor

@elharo elharo left a comment

Choose a reason for hiding this comment

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

"Use modern Java collections API (toList() instead of collect(Collectors.toList()))" is riskier than the other changes because of the move of mutable to immutable lists. Nine times out of ten this is fine. The tenth time it's a bear to debug. These changes should be pulled out into a separate PR.

The rest could be split, but maybe don't have to be. Nuulable and Nonnull might also call for a separate PR.

@Pankraz76
Copy link
Contributor

Pankraz76 commented May 5, 2025 via email

@Pankraz76
Copy link
Contributor

Regarding point 2. (and the related point 4.): I consider the IAE more concise than an NPE in case a method argument is passed with null (although not allowed). Compare also with https://discuss.kotlinlang.org/t/why-does-requirenotnull-throw-an-illegalargumentexception/7617/4

Java and Lombok prefer NPE as well.

https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html#requireNonNull-T-

https://projectlombok.org/features/NonNull

both valid points. Ideally its just one config-switch to adjust:

image

@gnodet
Copy link
Contributor Author

gnodet commented May 11, 2025

There 's no plan to use Lombok at this point.

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.

5 participants