Help AI coding agents use Java Optional well in new code and cleanups, without replacing one antipattern with another.
100
100%
Does it follow best practices?
Impact
100%
2.08xAverage score across 4 eval scenarios
Passed
No known issues
AI agents often know enough Java to reach for Optional, but not enough to use it well.
They write code that looks modern at first glance, then leaves you with isPresent() plus get(),
orElse(null), fallback code that runs too early, fake one-item lists, or a clear stream rewritten
as a noisy loop.
This skill gives the agent a small decision guide before it writes or changes Optional code: choose the Optional shape, run fallback work only when needed, keep real collection streams readable, and use a plain branch when checked IO makes that clearer.
It also tells the agent to check the project Java version first. The right Optional code for a Java 8 project may be different from the right code for Java 17 or Java 21.
Install the published Tessl plugin using the option that fits your setup:
| Tool | Command |
|---|---|
| npm | npx tessl i martinfrancois/java-optionals |
| yarn | yarn dlx tessl i martinfrancois/java-optionals |
| pnpm | pnpx tessl i martinfrancois/java-optionals |
| bun | bunx tessl i martinfrancois/java-optionals |
| Tessl CLI | tessl i martinfrancois/java-optionals |
Use one of the package-runner commands if you want to try the skill without installing the Tessl CLI first.
Agents that support skill auto-selection, such as
Codex and
Claude Code, can choose this skill automatically from the
task or code context. The task doesn't need to say Optional by name.
It can also trigger when Java code deals with missing values, values that may be null,
fallback/default values, isPresent(), orElse(null), optional.stream(),
findFirst() / findAny(), or similar code paths for values that may or may not exist.
For important Optional-heavy work, you can still name the skill explicitly:
Use $java-optionals to implement this Java feature with Optional best practices.For cleanup work:
Use $java-optionals to clean up this Java method without changing its outputs or error handling.For reviews:
Use $java-optionals to review this Java Optional code and suggest any cleanups.The motivation was real AI-written Java code. The agent already used Optional, but didn't follow
best practices.
Sometimes the agent introduced weak Optional code while writing a new feature:
// what an unassisted AI would write in new code
Optional<Coupon> coupon = findCoupon(code);
if (coupon.isPresent()) {
return applyCoupon(cart, coupon.get());
}
return cart;Other times, when asked to clean up the code and follow Optional best practices, it swapped one bad pattern for another:
isPresent() / get() with orElse(null) and local null checks;isPresent() or isEmpty() and then reading the same value with get() or
orElseThrow();orElse(...) where orElseGet(...) is required;findFirst() / findAny() by accident.The examples below are anti-examples. They show Java Optional code an AI agent would write,
followed by what it would change that code to when asked to follow Optional best practices without
this skill.
For example, one cleanup would have changed a simple coupon branch into a fake list:
// before the AI cleanup request
if (selectedCoupon.isPresent()) {
return applyCoupon(cart, selectedCoupon.get());
}
return cart;
// what an unassisted AI would have changed it to
List<Coupon> coupons = selectedCoupon.stream().toList();
if (!coupons.isEmpty()) {
return applyCoupon(cart, coupons.get(0));
}
return cart;Another would have changed a product discount fallback into local null checks:
// before the AI cleanup request
if (discount.isPresent()) {
return price.minus(discount.get());
}
return price;
// what an unassisted AI would have changed it to
Money value = discount.orElse(null);
if (value != null) {
return price.minus(value);
}
return price;And another would have changed a delivery-slot lookup into a harder-to-read loop:
// before the AI cleanup request
for (DeliveryWindow preferredWindow : preferredWindows) {
Optional<DeliverySlot> match = availableSlots.stream()
.filter(slot -> slot.fits(preferredWindow))
.findFirst();
if (match.isPresent()) {
return Optional.of(match.orElseThrow());
}
}
return Optional.empty();
// what an unassisted AI would have changed it to
for (DeliveryWindow preferredWindow : preferredWindows) {
for (DeliverySlot slot : availableSlots) {
if (slot.fits(preferredWindow)) {
return Optional.of(slot);
}
}
}
return Optional.empty();The goal isn't to force every branch into a method chain. The goal is simpler: understand what the
Optional is doing, keep the important effects in the same places, and choose the clearest code.
Without this skill, agents may "clean up" Optional code but still leave the same control-flow problem:
Coupon coupon = cart.coupon().orElse(null);
if (coupon != null) {
return totalWithCoupon(cart, coupon);
}
return totalWithoutCoupon(cart);With this skill, the agent is pushed toward using the Optional for the actual decision:
Money total(Cart cart) {
return cart.coupon()
.map(coupon -> totalWithCoupon(cart, coupon))
.orElseGet(() -> totalWithoutCoupon(cart));
}Good fit:
isPresent() or isEmpty() followed by get() or orElseThrow();orElse(null) followed by local null checks;orElse(...) and orElseGet(...);OptionalInt, OptionalLong, and OptionalDouble without boxing or getAs*() reopening;findFirst() or findAny() keeps the same result;optional.stream().toList() loops for a single Optional;null for missing values;Poor fit:
Optional;Simple fallback:
String customerName(Optional<Customer> customer) {
return customer.map(Customer::name).orElse("Guest");
}Create only when needed:
Cart cart(String cartId) {
return carts.find(cartId).orElseGet(() -> createCart(cartId));
}Side-effect branch:
void sendReceipt(Order order, Optional<Email> email) {
email.ifPresentOrElse(
address -> sendEmail(address, order),
() -> printReceipt(order));
}Checked IO case where a plain branch is clearer:
String shippingAddress(Checkout checkout, Console console) throws IOException {
Optional<String> saved = checkout.savedShippingAddress();
// Presence read is intentional here because the empty branch performs checked IO.
if (!saved.isPresent()) {
return console.readLine("Shipping address: ");
}
return saved.get();
}This is a narrow checked-IO boundary exception. Don't copy this shape for ordinary in-memory value
flow; use map, flatMap, orElseGet, or orElseThrow when both branches are ordinary Optional
value flow.
Real collection lookup:
Optional<DeliverySlot> deliverySlot(DeliveryWindow preferredWindow) {
return availableSlots.stream()
.filter(slot -> slot.fits(preferredWindow))
.findFirst();
}The skill is tested on implementation tasks based on real AI-written Optional mistakes. The tasks
cover both writing new Optional code and cleaning up existing Optional code. The headline suite uses
a documented mix of natural prompts and explicit Use $java-optionals prompts. Each task is run
without the skill and with the skill, then scored mainly on Optional-specific quality, with compile
and behavior checks included as safety checks.
The evals check that agents:
isPresent() / get() for ordinary value reads;Optional with orElse(null);Compile and behavior checks make regressions visible in the score, but the headline score is intentionally weighted toward Optional quality: whether the agent keeps the same behavior while avoiding Optional antipatterns and readability regressions.
Results should be read by subset:
$java-optionals;evals-reference/ keeps broader regression cases, including scenarios a strong baseline may
already solve.Current published scores are shown on the Tessl plugin.
Want to improve the skill, evals, or package metadata? See CONTRIBUTING.md.
This skill is based on real-world failures where coding agents wrote weak Java Optional code or
changed existing Optional code into different bad shapes while working on production-style tasks. The
motivating discussion is
martin-francois/symphony-trello#96.
The skill is self-contained: using it doesn't require access to that issue, the original repository, development drafts, or any external article.
MIT. See LICENSE.