Skip to content

Conversation

@aidanow-uni
Copy link

Thanks for your contribution to Apache Commons! Your help is appreciated!

Before you push a pull request, review this list:

  • Read the contribution guidelines for this project.
  • Read the ASF Generative Tooling Guidance if you use Artificial Intelligence (AI).
  • Run a successful build using the default Maven goal with mvn; that's mvn on the command line by itself.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best-practice.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process.

Description of Issue
The PR addresses the JIRA issue titled 'Short-circuit operation of firstNonBlank and firstNonEmpty', a minor feature request originally proposed by Zhengkai Wang.

The existing methods firstNonBlank and firstNonEmpty in StringUtils.java evaluate all input strings immediately. This can lead to extra computational overhead when some arguments are expensive to compute, such as those involving remote API calls or database queries.

The feature request aims to introduce supplier-based variants of these methods, which support lazy evaluation and short-circuit behaviour.

Implementation
To resolve this, two new methods were added to StringUtils:

  1. firstNonBlankSupplier(Supplier... suppliers)
    • Iterates through suppliers in order
    • Returns immediately once a non-blank supplier is found
  2. firstNonEmptySupplier(Supplier... suppliers)
    • Similar to above, but checks for non-empty rather than non-blank strings.

Introducing new methods instead of modifying existing ones ensures backward compatibility with existing firstNonBlank and firstNonEmpty implementations. It avoids method overloading ambiguity and also creates a clear separation between eager and lazy variants.

Validation and Testing

  1. Basic functionality tests
    • Implemented in StringUtilsEmptyBlankTest.
    • Methods testFirstNonBlankSupplier() and testFirstNonEmptySupplier() validate:
      • Correct return of the first non-blank/non-empty string.
      • Handling of null inputs, empty arrays, and suppliers that return blank or empty values.
  2. Lazy evaluation tests
    • testFirstNonBlankSupplierLazyEvaluation() and testFirstNonEmptySupplierLazyEvaluation() verify short-circuit behavior using AtomicBoolean flags.
    • Confirms that once a valid result is found, subsequent suppliers are not invoked, ensuring performance efficiency.
  3. Safety guarantees
    • All methods include null checks to prevent NullPointerException.
    • Edge cases covered in original tests are preserved.

Introduced a new method:
- StringUtils.firstNonBlankSupplier(Supplier<String>... suppliers)

This variant of firstNonBlank accepts suppliers evaluated lazily in order until a non-blank value is found. It enables short-circuit behavior to avoid unnecessary or expensive computations.
Introduced a new method:
- StringUtils.firstNonEmptySupplier(Supplier<String>... suppliers)

This variant of firstNonEmpty accepts suppliers evaluated lazily in order until a non-empty value is found. It enables short-circuit behavior to avoid unnecessary or expensive computations.
Fixed a minor typo on method name:
- StringUtils.firstNonEmptySuppler -> StringUtils.firstNonEmptySupplier
Added unit tests in StringUtilsEmptyBlankTest to verify:
  - StringUtils.firstNonBlankSupplier()
  - StringUtils.firstNonEmptySupplier()

For each method, test:
  - Correct return values
  - null handling
  - Lazy evaluation / short-circuit behaviour
Fixed Checkstyle issues:
 - Local variables are declared 'final'
 - Reordered imports (Static imports go first)
 - LeftCurly '{' fixes
@garydgregory
Copy link
Member

@garydgregory
Copy link
Member

I don't see the need for such a utility. Can this even be reused in Lang? What's a use-case in another code base, like any other Commons component?

@aidanow-uni
Copy link
Author

Thanks for the feedback. I understand your concerns about reusability.

There is actually prior use of this pattern in other Commons components. For example, org.apache.commons.cli.CommandLine in Commons-CLI defines multiple getOptionValue and getParsedOptionValue overloads that accept a Supplier as a lazy default value.

This shows that Commons libraries already adopt the Supplier pattern for deferred evaluation.

The proposed StringUtils.firstNonBlankSupplier() and firstNonEmptySupplier() generalise this concept: instead of one fallback supplier, they support a sequence of suppliers, evaluated lazily until a non-blank result is found.

If the team feels the method is too niche for Lang, another approach is to mark it as “advanced/optional” in the javadoc, or maybe present it as part of a “supplier”-variants section. But I believe its generic nature warrants inclusion.

Added Generic type T to methods firstNonBlankSupplier() and firstNonEmptySupplier().
- Matches original function
- Helps with scalability and backwards compatibility.
@sebbASF
Copy link
Contributor

sebbASF commented Oct 25, 2025

I'm not convinced that there is a good use case for this. Please can some real-world examples be provided?

@aidanow-uni
Copy link
Author

aidanow-uni commented Oct 25, 2025

A concrete use case for StringUtils.firstNonBlankSupplier() exists in the assignOneCluster method of the SinkServiceImpl class in Apache Inlong.

Both assignFromExist() and assignFromRelated() are evaluated eagerly, even though firstNonBlank will only use the first non-blank result. These methods can be non-trivial (e.g., database lookups or network calls).

Using the new supplier-based version would avoid that overhead:

return StringUtils.firstNonBlankSupplier(
() -> assignFromExist(sinkInfo.getDataNodeName(), group.getInlongClusterTag()),
() -> assignFromRelated(sinkInfo.getSinkType(), group)
);

Fixes to javadoc warnings.
- Added params for <T>
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.

3 participants