The Ternary Operator

Choose between two values in one expression with ?:, and avoid the unboxing and numeric promotion traps that make it throw or change types.

SyntaxType value = condition ? valueIfTrue : valueIfFalse;

The conditional operator condition ? a : b is an expression: it evaluates the condition, then evaluates and returns either a or b, never both. It is the right tool when you need one of two values inline: a default, a plural, a label in a log line.

int page = pageParam == null ? 1 : Integer.parseInt(pageParam);
String word = count == 1 ? "item" : "items";

It is not a statement

ok ? save() : log(); does not compile; a conditional expression cannot stand on its own. If you want to run one of two actions, use if/else. Use the ternary only when you need the value.

Precedence

?: binds more loosely than + and comparison operators. "status=" + stock > 0 ? "IN_STOCK" : "SOLD_OUT" tries to compare a string with a number and fails to compile. Wrap the conditional in parentheses whenever it is part of a bigger expression.

The result has one type

The two branches are combined into a single type, and the rules surprise people:

  • If one branch is int and the other double, the result is double. Assigning it to Object gives you 3.0, not 3, which shows up in hand-built JSON.
  • If one branch is Integer and the other a primitive int, the result is int. When the Integer branch is null, Java unboxes it and throws a NullPointerException, even if you assign the result to an Integer. This happens with map.get(key) on one side and a literal default on the other.

Make both branches the same type to avoid both traps. For map defaults, map.getOrDefault(key, 0) is usually clearer anyway.

Keep it short

A ternary nested inside another one is hard to read, and nobody can set a breakpoint on one branch. If you have three or more outcomes, write an if chain or a switch expression. A good rule: if the line wraps, it should not be a ternary.

In back-end code ternaries earn their place in small decisions: choosing a default page size, picking a log level, adding an s to a message, or choosing between 200 and 201 depending on whether a record was created.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Return 201 when a PUT created a record and 200 when it updated one.
  • Fall back to page 1 or a default page size when a query parameter is missing.
  • Write log and error messages with the correct singular or plural noun.

More examples

Unboxing a null in a mixed ternary

The declared type on the left does not matter; the branch types decide. Making both branches Integer keeps the null, which you can then handle explicitly.

Example · java
void main() {
    Map<String, Integer> overrides = new HashMap<>();
    overrides.put("trial", null); // HashMap allows null values

    try {
        // Integer and int branches: the result type is int, so null is unboxed.
        Integer limit = overrides.containsKey("trial") ? overrides.get("trial") : 0;
        IO.println("limit=" + limit);
    } catch (NullPointerException e) {
        IO.println("NullPointerException from unboxing");
    }

    // Both branches Integer: the result stays a reference and may be null.
    Integer safe = overrides.containsKey("trial") ? overrides.get("trial") : Integer.valueOf(0);
    IO.println("safe=" + safe);
}
// Output:
// NullPointerException from unboxing
// safe=null

Numeric promotion changes the value's type

With int and double branches, the int is widened to double before it is boxed, so a JSON field would read 3.0. Casting one branch to Object stops the promotion and each branch keeps its own type.

Example · java
void main() {
    boolean wholeNumber = true;
    int count = 3;
    double average = 2.5;

    Object value = wholeNumber ? count : average;
    IO.println(value);
    IO.println(value.getClass().getSimpleName());

    Object fixed = wholeNumber ? (Object) count : average;
    IO.println(fixed);
    IO.println(fixed.getClass().getSimpleName());
}
// Output:
// 3.0
// Double
// 3
// Integer

Nested ternary versus switch

Both give the same answers for valid status codes, but the switch reads as a table and is easier to extend. Laid out one condition per line, a nested ternary is readable; on one line it is not.

Example · java
String classOf(int code) {
    return code < 200 ? "informational"
         : code < 300 ? "success"
         : code < 400 ? "redirect"
         : code < 500 ? "client error"
         : "server error";
}

String classOfSwitch(int code) {
    return switch (code / 100) {
        case 1 -> "informational";
        case 2 -> "success";
        case 3 -> "redirect";
        case 4 -> "client error";
        default -> "server error";
    };
}

void main() {
    for (int code : new int[] {101, 200, 301, 404, 503}) {
        IO.println(code + " " + classOf(code) + " / " + classOfSwitch(code));
    }
}
// Output:
// 101 informational / informational
// 200 success / success
// 301 redirect / redirect
// 404 client error / client error
// 503 server error / server error

Discussion

  • Be the first to comment on this lesson.