Skip to content

[java] remove @Nullable from return value for ExpectedConditions that never return null#17149

Open
asolntsev wants to merge 1 commit intoSeleniumHQ:trunkfrom
asolntsev:fix/nullable-return-values-causing-kotlin-error
Open

[java] remove @Nullable from return value for ExpectedConditions that never return null#17149
asolntsev wants to merge 1 commit intoSeleniumHQ:trunkfrom
asolntsev:fix/nullable-return-values-causing-kotlin-error

Conversation

@asolntsev
Copy link
Contributor

@asolntsev asolntsev commented Feb 28, 2026

🔗 Related Issues

Fixes #17122

💥 What does this PR do?

  1. Removes @Nullable annotation from several return value for ExpectedConditions that never return null
  2. Marks type of Function both nullable and non-nullable (using a crazy trick <V extends @Nullable Object>).

🔧 Implementation Notes

  1. Some implementations of ExpectedConditions can return null.
  2. But expression wait.until(ExpectedConditions.visibilityOf(...)) never returns null. It either returns Object or true, or throws TimeoutException.

💡 Additional Considerations

Probably we should create a test suite in Kotlin, so that nullability problems arise earlier.

🔄 Types of changes

  • Bug fix (backwards compatible)

@asolntsev asolntsev added this to the 4.42.0 milestone Feb 28, 2026
@selenium-ci selenium-ci added C-java Java Bindings B-support Issue or PR related to support classes labels Feb 28, 2026
@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Feb 28, 2026

PR Type

(Describe updated until commit 8e135bd)

Bug fix


Description

  • Fix nullability annotations in Wait.until() to prevent Kotlin compilation errors

  • Change return type from nullable to non-nullable while accepting both nullable and non-nullable function implementations

  • Use generic type bounds <V extends @Nullable Object> to satisfy both Kotlin and Java type systems

  • Remove redundant type parameters from anonymous ExpectedCondition class instantiations

  • Initialize uninitialized fields to prevent potential issues


File Walkthrough

Relevant files
Bug fix
Wait.java
Update Wait interface until() method nullability signature

java/src/org/openqa/selenium/support/ui/Wait.java

  • Add @NonNull and @Nullable imports from jspecify
  • Change until() method signature to use
    with @NonNull V return type
  • Update parameter type to Function for
    covariance
  • Fix javadoc parameter name from to
+4/-2     
FluentWait.java
Update FluentWait until() implementation to match interface

java/src/org/openqa/selenium/support/ui/FluentWait.java

  • Add @NonNull import from jspecify
  • Update until() method implementation to match new signature with
    extends @Nullable Object> and @NonNull V return type
  • Change parameter type to Function for
    covariance
+2/-1     
Code cleanup
ExpectedConditions.java
Simplify type parameters and initialize fields in ExpectedConditions

java/src/org/openqa/selenium/support/ui/ExpectedConditions.java

  • Remove explicit type parameters from 12 anonymous ExpectedCondition
    class instantiations (use diamond operator <>)
  • Initialize previously uninitialized fields with default values (-1 for
    int fields)
  • Affected methods: visibilityOfAllElementsLocatedBy(),
    visibilityOfAllElements(), visibilityOf(),
    presenceOfAllElementsLocatedBy(), frameToBeAvailableAndSwitchToIt()
    (both overloads), refreshed(), alertIsPresent(),
    numberOfWindowsToBe(), numberOfElementsToBeMoreThan(),
    numberOfElementsToBeLessThan(), numberOfElementsToBe(),
    visibilityOfNestedElementsLocatedBy() (both overloads),
    presenceOfNestedElementsLocatedBy(), invisibilityOfAllElements(),
    jsReturnsValue()
+19/-19 

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@asolntsev asolntsev added I-defect Something is not working as intended I-regression Something was working but we "fixed" it and removed Review effort 2/5 labels Feb 28, 2026
@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Learned
best practice
Fix misleading nullability contract

Revert the generic return type back to ExpectedCondition<@Nullable ...> (or
change the implementation to never return null) so callers and nullness tools
don’t get a misleading non-null contract.

java/src/org/openqa/selenium/support/ui/ExpectedConditions.java [187-197]

-public static ExpectedCondition<WebElement> presenceOfElementLocated(final By locator) {
+public static ExpectedCondition<@Nullable WebElement> presenceOfElementLocated(final By locator) {
   return new ExpectedCondition<>() {
     @Override
     @Nullable
     public WebElement apply(WebDriver driver) {
       try {
         return driver.findElement(locator);
       } catch (NoSuchElementException notFound) {
         return null;
       }
     }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Ensure API contracts (especially nullability) match actual behavior; don’t declare non-null return types when implementations can return null.

Low
Align generic nullability with behavior

Change the method signature to ExpectedCondition<@Nullable T> (or enforce
non-null results) to reflect that refreshed can intentionally return null while
waiting.

java/src/org/openqa/selenium/support/ui/ExpectedConditions.java [851-859]

-public static <T> ExpectedCondition<T> refreshed(final ExpectedCondition<T> condition) {
+public static <T> ExpectedCondition<@Nullable T> refreshed(final ExpectedCondition<T> condition) {
   return new ExpectedCondition<>() {
     @Override
     public @Nullable T apply(WebDriver driver) {
       try {
         return condition.apply(driver);
       } catch (StaleElementReferenceException | NoSuchElementException e) {
         return null;
       }
     }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Keep generic nullability consistent with returned values; avoid widening non-null generics when the condition is allowed to return null.

Low
  • More

@asolntsev asolntsev marked this pull request as draft February 28, 2026 16:59
... and <V> type of Function both nullable and non-nullable.

Expression `wait.until(condition)` never returns null. It either returns an Object or true, or throws TimeoutException. While `Function<T, V> isTrue` can return null.

Fixes SeleniumHQ#17122

P.S. This is a crazy trick to satisfy Kotlin compiler:
```java
<V extends @nullable Object> @nonnull V until(Function<? super F, ? extends V> isTrue);
```

Thanks to `<V extends @nullable Object>`, Kotlin compiler accepts both nullable and non-nullable functions:
* `(Function<? super T, V> isTrue)`
* `(Function<? super T, @nullable V> isTrue)`
@asolntsev asolntsev force-pushed the fix/nullable-return-values-causing-kotlin-error branch from f5e6ad3 to 8e135bd Compare February 28, 2026 21:30
@asolntsev asolntsev marked this pull request as ready for review February 28, 2026 21:31
@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Learned
best practice
Fix nullness in method signature

Remove the nullable upper-bound on V and instead annotate the function’s return
as nullable, so the API expresses “the predicate may return null, but until
never does”.

java/src/org/openqa/selenium/support/ui/Wait.java [45]

-<V extends @Nullable Object> @NonNull V until(Function<? super F, ? extends V> isTrue);
+<V> @NonNull V until(Function<? super F, @Nullable ? extends V> isTrue);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Avoid contradictory nullness contracts; if a method guarantees non-null, don’t bind its type variable to nullable.

Low
Align implementation nullness contract

Match the interface’s intended contract by removing the nullable bound on V and
marking the function return as nullable, while keeping until itself @NonNull.

java/src/org/openqa/selenium/support/ui/FluentWait.java [204-205]

-public <V extends @Nullable Object> @NonNull V until(Function<? super T, ? extends V> isTrue) {
+public <V> @NonNull V until(Function<? super T, @Nullable ? extends V> isTrue) {
   Instant end = clock.instant().plus(timeout);
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Keep nullness annotations consistent between interface and implementation to prevent confusing or unsound contracts.

Low
General
Initialize counters to –1

Initialize the actualNumber field in numberOfElementsToBeMoreThan to -1 for
consistency with other counter-based conditions.

java/src/org/openqa/selenium/support/ui/ExpectedConditions.java [1137-1140]

     public static ExpectedCondition<@Nullable List<WebElement>> numberOfElementsToBeMoreThan(
         final By locator, final Integer expectedNumber) {
       return new ExpectedCondition<>() {
-        private Integer actualNumber = 0;
+        private Integer actualNumber = -1;
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies an inconsistency in initializing counter variables and proposes a change that improves consistency across the codebase.

Low
  • More

@iampopovich
Copy link
Contributor

changes came from #16489 .
It added JSpecify null-safety annotations to the expected conditions classes. conditions do return null as a "not yet ready" signal. However, the annotations accidentally leaked into the public contract of the until() method, even though it never returns null to the caller. it either returns a value or throws TimeoutException. seems like Java silently accepts this, Kotlin does not: the code stopped compiling.

@SeleniumHQ SeleniumHQ deleted a comment from selenium-ci Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-support Issue or PR related to support classes C-java Java Bindings I-defect Something is not working as intended I-regression Something was working but we "fixed" it Review effort 2/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🐛 Bug]: [java] FluentWait.until(ExpectedConditions.…(…)) cannot be called from Kotlin

3 participants