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.
Type 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
intand the otherdouble, the result isdouble. Assigning it toObjectgives you3.0, not3, which shows up in hand-built JSON. - If one branch is
Integerand the other a primitiveint, the result isint. When theIntegerbranch isnull, Java unboxes it and throws aNullPointerException, even if you assign the result to anInteger. This happens withmap.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
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.
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=nullNumeric 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.
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
// IntegerNested 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.
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