openrewrite / openrewrite/rewrite-migrate-java
OptionalNotPresentToIsEmpty rewrites `!isPresent()` on a non-`java.util.Optional` type, producing uncompilable `isEmpty()`
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 156
- Forks
- 130
- Avg merge
- 20h 57m
- Merged PRs (30d)
- 21
Description
What version of OpenRewrite are you using?
- Gradle plugin
org.openrewrite:pluginv7.35.0 - rewrite-migrate-java v3.38.0
- Active recipe:
org.openrewrite.java.migrate.UpgradeToJava21(which pulls inorg.openrewrite.java.migrate.util.OptionalNotPresentToIsEmpty)
How are you running OpenRewrite?
Gradle plugin, applied via an init script over a large multi-module project:
rootProject {
plugins.apply(org.openrewrite.gradle.RewritePlugin)
dependencies {
rewrite("org.openrewrite.recipe:rewrite-migrate-java:3.38.0")
}
rewrite {
activeRecipe("org.openrewrite.java.migrate.UpgradeToJava21")
setExportDatatables(true)
}
}
The affected module depends on Jolt (com.bazaarvoice.jolt:jolt-core:0.1.8), which ships its own
com.bazaarvoice.jolt.common.Optional<T>. That class exposes isPresent() and get() but has no
isEmpty() method (its full public API is empty(), of(T), get(), isPresent(), equals, toString).
The project is private, but the third-party type is public and small; the relevant snippet is below.
What is the smallest, simplest way to reproduce the problem?
The receiver is a Jolt Optional, not a JDK Optional:
import com.bazaarvoice.jolt.common.Optional; // NOT java.util.Optional
class ConvertMarkdownToHtmlRTL {
Optional<Object> applySingle(Object input) {
Optional<Object> baseHtmlOpt = convert(input);
if (!baseHtmlOpt.isPresent()) { // original, compiles fine
return Optional.empty();
}
// ...
}
}
What did you expect to see?
The code left unchanged. baseHtmlOpt is a com.bazaarvoice.jolt.common.Optional, not a
java.util.Optional, so the recipe should not apply. The recipe's MethodMatcher is in fact scoped to the
JDK type — new MethodMatcher("java.util.Optional isPresent()") with template
#{any(java.util.Optional)}.isEmpty()
(OptionalNotPresentToIsEmpty.java, lines 46 and 56 on v3.38.0) — so the intent is clearly JDK-only.
What did you see instead?
The recipe rewrote it to call isEmpty() on the Jolt type, which does not have that method, so the module
no longer compiles:
import com.bazaarvoice.jolt.common.Optional;
class ConvertMarkdownToHtmlRTL {
Optional<Object> applySingle(Object input) {
Optional<Object> baseHtmlOpt = convert(input);
if (baseHtmlOpt.isEmpty()) { // does not compile: no isEmpty() on com.bazaarvoice.jolt.common.Optional
return Optional.empty();
}
// ...
}
}
Because the JDK-scoped MethodMatcher nevertheless matched a com.bazaarvoice.jolt.common.Optional
receiver, this looks like the matcher firing when the receiver's type is unresolved/incompletely attributed
(the third-party Optional not being on the recipe's typed classpath), falling through to a name-only match
on isPresent(). A type-scoped migration recipe should not rewrite when it cannot positively confirm the
receiver is java.util.Optional — otherwise it silently emits uncompilable code with no warning or error from
the run itself.
- Note this is a distinct failure mode from #417/#418 (a
ClassCastExceptionthrown by the sibling
OptionalNotEmptyToIsPresent); here there is no crash — the recipe completes "successfully" and the breakage
only surfaces at the nextcompileJava.
What is the full stack trace of any errors you encountered?
No stack trace — the recipe run completes without error. The failure is a downstream Java compile error:
error: cannot find symbol
if (baseHtmlOpt.isEmpty()) {
^
symbol: method isEmpty()
location: variable baseHtmlOpt of type com.bazaarvoice.jolt.common.Optional<Object>
Are you interested in contributing a fix to OpenRewrite?
Open to it if you can point me at the preferred guard (e.g. requiring resolved receiver type before applying). Happy to add a recipe test that reproduces the mis-fire on a non-JDK Optional.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in OptionalNotPresentToIsEmpty.java, especially the MethodMatcher and rewrite template referenced at lines 46 and 56. Reproduce the transformation with com.bazaarvoice.jolt.common.Optional, then add a regression test showing that unresolved or non-JDK receivers remain unchanged and that java.util.Optional still rewrites correctly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100