Learn Java

Lesson 4Phase 1: First steps

Making decisions: if, else and switch

Choosing between paths with if and else, and picking one of many cases with switch statements and switch expressions.

By Updated 55 min

  • if
  • else if
  • boolean conditions
  • switch
  • fall-through
  • ternary

Until now every program you wrote ran straight down the page, top to bottom, every line every time. Real programs are not like that. They look at what is true right now and choose what to do next. That choosing is called a decision, and this lesson is about the handful of ways Java lets you write one.

By the end of this lesson
  • Write an if that runs a block of code only when a condition is true.
  • Chain choices with else if and else, and predict which branch runs.
  • Build a condition from comparisons and &&, || and !, and avoid the = versus == trap.
  • Use the ternary cond ? a : b to choose between two values.
  • Write a classic switch with break, and explain fall-through.
  • Write the arrow form of switch and switch expressions that produce a value, using yield when a case needs several lines.
From before
This lesson leans on operators, expressions, and casting. In particular you need the comparison operators (<, <=, >, >=, ==, !=) and the logical operators (&&, ||, !), all of which produce a boolean. We use only those plus variables and types. No loops or arrays appear here yet.

The if statement runs a block when something is true#

The smallest decision in Java is the if statement. You give it a condition in parentheses and a block of code in braces. If the condition is true, the block runs. If it is false, the block is skipped and the program carries on below it.

Main.java
1public class Main {
2 public static void main(String[] args) {
3 int temperature = 35;
4
5 if (temperature > 30) {
6 System.out.println("It is hot today.");
7 }
8
9 System.out.println("Have a good day.");
10 }
11}
Prints
›It is hot today.
›Have a good day.

Read it slowly. temperature is 35, so temperature > 30 is true, and the line inside the braces runs. The last line is outside the braces, so it always runs no matter what. Set temperature to 20 and the first message disappears while the second stays. The braces mark exactly which lines belong to the decision.

Key idea

The thing inside the parentheses is not a sentence Java reads in English. It is an expression that evaluates to a single boolean, either true or false. The if only cares about that one value.

Coming from Python, JavaScript or C?
Those languages let a number stand in for true or false: a condition that is just count is true whenever count is not zero. Java accepts only a boolean, so you write the comparison out: if (count != 0).
Braces are optional for one line, but use them anyway
If the block is a single statement, Java lets you drop the braces: if (x > 0) System.out.println("positive");. It works, but it invites a classic mistake: you add a second line later, indent it to match, and expect it to belong to the if. Without braces, only the first statement does. Always write the braces. They cost two characters.
Predict the outputStock.java
1public class Main {
2 public static void main(String[] args) {
3 int stock = 12;
4
5 if (stock < 5)
6 System.out.println("Running low");
7 System.out.println("Order more today");
8
9 System.out.println("Stock: " + stock);
10 }
11}

There are no braces after this if, and the second print is indented like the first. What does the program print?

It prints Order more today and then Stock: 12. The condition stock < 5 is false, so the if skips its statement, and without braces its statement is only the first print. The second print merely looks like part of the if. Java ignores indentation, so that line runs every time.

else and else if: choosing between branches#

An ifon its own answers a yes-or-no question. To say “do this, otherwise do that” you add an else. The else block runs exactly when the if condition was false. Together they form a fork in the road: exactly one of the two blocks runs.

Main.java
1public class Main {
2 public static void main(String[] args) {
3 int age = 16;
4
5 if (age >= 18) {
6 System.out.println("You may vote.");
7 } else {
8 System.out.println("Too young to vote.");
9 }
10 }
11}
Prints
›Too young to vote.

Often you have more than two cases. For that you stack conditions with else if. Java checks each condition from the top down and runs the block of the first one that is true. As soon as one matches, the rest are skipped entirely. A final bare else catches everything that fell through.

Sign.java
1public class Main {
2 public static void main(String[] args) {
3 int n = -7;
4
5 if (n < 0) {
6 System.out.println(n + " is negative");
7 } else if (n == 0) {
8 System.out.println(n + " is zero");
9 } else {
10 System.out.println(n + " is positive");
11 }
12 }
13}
Prints
›-7 is negative

Press Run and step through. Watch the order: Java tests n < 0 first. Because n is -7, that is true, so the negative branch runs and the other two are never even looked at. The output is -7 is negative. With n at 0, the first test would fail, the second would succeed, and you would get the zero branch. The order of the tests is the order of the checking.

Key idea

In an if / else if / else chain, exactly one branch runs: the first whose condition is true, or the final else if none of them are. Order matters, because checking stops at the first match.

Quick check
A chain first tests temp > 30 and prints hot, then tests temp > 20 and prints warm, and otherwise prints cold. What does it print when temp is 30?

A worked example: a grade from a score#

Here is a fuller example. A score from 0 to 100 becomes a letter grade with the usual thresholds: 90 and up is an A, 80 and up a B, 70 and up a C, 60 and up a D, anything lower an F. Each branch can assume the higher ones already failed, so it needs only a single lower bound.

Grade.java
1public class Main {
2 public static void main(String[] args) {
3 int score = 73;
4 char grade;
5
6 if (score >= 90) {
7 grade = 'A';
8 } else if (score >= 80) {
9 grade = 'B';
10 } else if (score >= 70) {
11 grade = 'C';
12 } else if (score >= 60) {
13 grade = 'D';
14 } else {
15 grade = 'F';
16 }
17
18 System.out.println("Score " + score + " earns grade " + grade);
19 }
20}
Prints
›Score 73 earns grade C

Trace it. With score at 73: 73 >= 90 is false, 73 >= 80 is false, 73 >= 70 is true, so grade becomes 'C' and the chain ends. The program prints Score 73 earns grade C. A score of 90 would give an A, 59 an F, and the boundary 80 a B, because the test is >=, not >.

Boundaries are where decisions go wrong
Most decision bugs hide at the edges. Is 80 a B or a C? With score >= 80 it is a B; with score > 80 it would slip down to a C. Whenever you write a threshold, pick one exact value sitting on the line and trace it by hand. The boundary is the case worth testing.

Trying other scores: reading the keyboard#

To see another grade, you would have to change 73 in the code and run it again. A program can ask for the score instead. This version reads whatever whole number is typed on the keyboard. The Keyboard input box under the code holds 80. Run it, then change the number to 79, 90 or 100 and run it again.

GradeInput.java
1import java.util.Scanner;
2
3public class Main {
4 public static void main(String[] args) {
5 Scanner in = new Scanner(System.in);
6 System.out.print("Score: ");
7 int score = in.nextInt();
8 char grade;
9
10 if (score >= 90) {
11 grade = 'A';
12 } else if (score >= 80) {
13 grade = 'B';
14 } else if (score >= 70) {
15 grade = 'C';
16 } else if (score >= 60) {
17 grade = 'D';
18 } else {
19 grade = 'F';
20 }
21
22 System.out.println("Score " + score + " earns grade " + grade);
23 }
24}
Prints
›Score: 80Score 80 earns grade B

Three new lines do the reading. Treat them as a recipe for now:

  • import java.util.Scanner; sits above the class. It tells Java the program uses Scanner, a ready-made class from Java's library whose job is reading input.
  • Scanner in = new Scanner(System.in); makes a scanner named in that reads from the keyboard. System.in is the keyboard, the way System.out is the screen.
  • int score = in.nextInt(); waits until a whole number is typed, then stores it in score.

With 80 typed, the console shows the prompt Score: with the 80 after it, then Score 80 earns grade B. How a scanner works, and what else it can read, is the subject of Scanner and console applications. Until then, these three lines are all you need.

Put the lines in orderParcel.java
A parcel service charges 1 for up to 100 grams, 3 for up to 500 grams, 7 for up to 2000 grams and 15 for anything heavier. Put the lines in order so that an 80 gram parcel costs 1. Think about which test has to come first.

Tap a line, then tap where it should go. Or use the arrows.

Conditions are just boolean expressions#

Because a condition is only an expression that produces a boolean, everything you learned about logical operators applies directly. You can join several tests into one with && (and), || (or), and ! (not). This keeps a chain short when a branch really depends on two facts at once.

Discount.java
1public class Main {
2 public static void main(String[] args) {
3 int age = 65;
4 boolean isMember = true;
5
6 if (age >= 60 && isMember) {
7 System.out.println("Senior member discount");
8 } else if (age >= 60 || isMember) {
9 System.out.println("Partial discount");
10 } else {
11 System.out.println("No discount");
12 }
13 }
14}
Prints
›Senior member discount

Here age >= 60 is true and isMember is already a boolean that is true, so age >= 60 && isMember is true and the first branch wins: Senior member discount. Notice isMember needs no comparison. It is already a yes-or-no value, so it can stand on its own inside the parentheses.

One equals sign assigns; two compare
The single most common decision bug is writing = where you meant ==. = puts a value into a variable; == asks whether two values are equal. Writing if (isMember = true) does not test isMember; it sets it to true and then uses that. Java catches this for plain numbers (if (x = 5) will not compile, because 5 is an int, not a boolean), but with a boolean variable it slips through. Always write == for a comparison, and test a boolean variable on its own: if (isMember).
Quick check
isMember is a boolean holding false, and count is an int. Which condition compiles but quietly does the wrong thing?
ExerciseLeapYear.java
A year is a leap year when it divides evenly by 4, except that years dividing evenly by 100 are not, unless they also divide evenly by 400. So 2024 is a leap year, 1900 is not, and 2000 is. Replace the TODO so that leap is true exactly for leap years. The program reads the year from the keyboard, and each test types a different one.

Nested ifs: a decision inside a decision#

You can put an if inside another if. The inner one is only reached when the outer condition is already true, so together they ask “is this true, and within that, is this other thing true?”. A classic use is finding the largest of three numbers.

Largest.java
1public class Main {
2 public static void main(String[] args) {
3 double a = -1.0;
4 double b = 4.5;
5 double c = -5.3;
6 double largest;
7
8 if (a >= b) {
9 if (a >= c) {
10 largest = a;
11 } else {
12 largest = c;
13 }
14 } else {
15 if (b >= c) {
16 largest = b;
17 } else {
18 largest = c;
19 }
20 }
21
22 System.out.println("Largest is " + largest);
23 }
24}
Prints
›Largest is 4.5

Read it as a tree. The outer if (a >= b) splits the problem in two. On the “yes” side we already know a beats b, so we only need to compare a against c. On the “no” side b beats a, so we compare b against c. Here b is the largest, so it prints Largest is 4.5.

Nesting versus combining
You could often write the same logic with && instead of nesting. Nesting reads best when the inner question only makes sense once the outer one is settled. If you find yourself nested three or four deep, that is usually a sign to step back and simplify, perhaps with combined conditions or, later, with methods.

The ternary: a compact if for choosing a value#

Some decisions pick a value rather than an action. You want max to be whichever of two numbers is larger, or a label to be one word or another. Writing a whole if / else just to set one variable feels heavy. Java has a one-line form for exactly this: the ternary operator, written condition ? a : b.

Read it as a question. The part before the ? is the condition. If it is true, the whole expression becomes the value after the ?. If it is false, it becomes the value after the :. It is the only operator in Java that takes three parts, which is where the name ternary comes from.

Ternary.java
1public class Main {
2 public static void main(String[] args) {
3 int age = 17;
4 String status = age >= 18 ? "adult" : "minor";
5 System.out.println("At " + age + " you are a " + status);
6
7 int x = -4;
8 int size = x < 0 ? -x : x;
9 System.out.println("The size of " + x + " is " + size);
10 }
11}
Prints
›At 17 you are a minor
›The size of -4 is 4

Two uses, side by side. In the first, age >= 18 is false (age is 17), so status becomes "minor" and we print At 17 you are a minor. In the second, x < 0 is true, so size becomes -x, which is 4, and we print The size of -4 is 4. That second line is the absolute value, written in a single expression.

Reach for ternary only when you are picking a value
The ternary is a value, so it belongs on the right of an = or inside a larger expression. It is not a place to run actions. If a branch needs to print, update several variables, or do real work, use a full if / else. The ternary suits the small case “set this one thing to A or B” and becomes hard to read the moment you try to stretch it further.

switch: one value, many cases#

A common kind of decision checks one variable against a list of exact values: if it is 1 do this, if it is 2 do that, and so on. An else if chain can do it, but it repeats the variable on every line. A switch names the value once and then lists the cases. Java has two ways to write the cases, an older one with colons and a newer one with arrows. You will meet both in real code, so this section covers both, starting with the older one.

The classic form: case, colon and break#

Day.java
1public class Main {
2 public static void main(String[] args) {
3 int day = 3;
4
5 switch (day) {
6 case 1:
7 System.out.println("Monday");
8 break;
9 case 2:
10 System.out.println("Tuesday");
11 break;
12 case 3:
13 System.out.println("Wednesday");
14 break;
15 default:
16 System.out.println("Another day");
17 }
18 }
19}
Prints
›Wednesday

Step through it. day is 3, so control jumps straight to case 3, prints Wednesday, and the break ends the switch. Each case label is the word case, a value and a colon. The default label is the catch-all, like the final else in a chain: it runs when no case matched. If day had been 9, you would see Another day.

Fall-through: the job break does#

That break after each case is doing real work. In the colon form, a case label only marks a place to start. Once Java has jumped to the matching label, it keeps running downward through the code of the following cases until it reaches a break or the end of the switch. That behavior is called fall-through. Forget one break and the program quietly does more than you meant.

Predict the outputMissingBreak.java
1public class Main {
2 public static void main(String[] args) {
3 int day = 2;
4
5 switch (day) {
6 case 1:
7 System.out.println("Monday");
8 break;
9 case 2:
10 System.out.println("Tuesday");
11 case 3:
12 System.out.println("Wednesday");
13 break;
14 default:
15 System.out.println("Another day");
16 }
17 }
18}

This is the day switch with one break missing, and day is now 2. What does it print?

It prints Tuesday and then Wednesday. Control lands on case 2 and prints Tuesday. There is no break there, so it falls through into case 3, prints Wednesday, and only stops at the break below that. The default is never reached.

Fall-through is sometimes useful. When several values should share one block, you stack their case labels with no code between them and let them all fall into a single body. Here both 'A' and 'B' lead to the same message.

Feedback.java
1public class Main {
2 public static void main(String[] args) {
3 char grade = 'B';
4
5 switch (grade) {
6 case 'A':
7 case 'B':
8 System.out.println("Well done");
9 break;
10 case 'C':
11 System.out.println("Solid pass");
12 break;
13 default:
14 System.out.println("Keep working");
15 }
16 }
17}
Prints
›Well done

With grade equal to 'B', control lands on case 'B'. There is no code directly under it, so it falls into the shared body and prints Well done, then break ends the switch. Since Java 14 you can also put both values in one label, case 'A', 'B':, which says the same thing more plainly.

Key idea

In the colon form, Java jumps to the matching case and keeps running until it meets a break. A forgotten break is the classic switch bug.

The arrow form: no fall-through#

Java 14 added a second way to write the cases. Replace the colon with an arrow, ->, and the code after the arrow is the whole case. Here is the feedback switch again in the arrow form:

FeedbackArrow.java
1public class Main {
2 public static void main(String[] args) {
3 char grade = 'B';
4
5 switch (grade) {
6 case 'A', 'B' -> System.out.println("Well done");
7 case 'C' -> System.out.println("Solid pass");
8 default -> System.out.println("Keep working");
9 }
10 }
11}
Prints
›Well done

It prints Well done, and there is no breakanywhere. None is needed: after an arrow, Java runs only that case's code and then leaves the switch, so there is nothing to fall through into. Values that share a case go in one label, separated by commas, as in case 'A', 'B' ->. To run several statements in one case, put them in braces after the arrow: case 'C' -> { ... }. One switch uses one form: mixing arrows and colons in the same switch is a compile error, “different case kinds used in the switch”.

Colon form
Arrow form
case 1: followed by statements and usually a break.
case 1 -> followed by one statement, a block in braces, or a value.
Falls through into the next case unless a break stops it.
Runs only its own case. There is nothing to break out of.
Several values: stacked labels, or case 1, 2:.
Several values: case 1, 2 ->.
Found in almost every older Java program.
Added in Java 14, and the usual choice in new code.

Switch expressions: a switch that produces a value#

Look at what the day switch does in every case: it prints a name. It could just as well choose the name and leave the printing for later. A switch can produce a value directly, the way the ternary does. Used like this it is called a switch expression:

DayName.java
1public class Main {
2 public static void main(String[] args) {
3 int day = 3;
4
5 String name = switch (day) {
6 case 1 -> "Monday";
7 case 2 -> "Tuesday";
8 case 3 -> "Wednesday";
9 default -> "Another day";
10 };
11
12 System.out.println(name);
13 }
14}
Prints
›Wednesday

The whole switch (day) { ... } now stands for a value, and that value goes into name. Each case says which value to produce: with day equal to 3 it is "Wednesday", so the program prints Wednesday. Compare it with the colon version above: no break, no repeated println, and the name ends up in a variable you can use afterwards. Note the semicolon after the closing brace. It ends the statement String name = switch (day) { ... };, the same way a semicolon ends any other assignment.

A switch expression must produce a value whatever day holds. An int can hold billions of values and the cases name only three, so the default is required. Leave it out and the program does not compile:

NoDefault.java (does not compile)
read along
1public class Main {
2 public static void main(String[] args) {
3 int day = 3;
4
5 String name = switch (day) { // error: the switch expression does not cover all possible input values
6 case 1 -> "Monday";
7 case 2 -> "Tuesday";
8 case 3 -> "Wednesday";
9 };
10
11 System.out.println(name);
12 }
13}

A switch statement has no such rule: if no case matches and there is no default, it simply does nothing. A switch expression must always come up with a value, so the compiler checks that every possible input is covered. For an int or a String, that means a default.

Key idea

A switch expression produces a value: each arrow names the value for its case, and a default covers everything else. After an arrow only that case runs, so there is no fall-through and no break.

yield: when a case needs more than one line#

Sometimes a case has to work something out before it knows its value. Then the arrow is followed by a block in braces, and the block ends with yield and the value the case produces. In this cinema, adults pay 12, and children and students pay 8, with 2 off on Tuesdays:

TicketPrice.java
1public class Main {
2 public static void main(String[] args) {
3 String ticket = "student";
4 boolean tuesday = true;
5
6 int price = switch (ticket) {
7 case "adult" -> 12;
8 case "child", "student" -> {
9 int base = 8;
10 if (tuesday) {
11 base = base - 2;
12 }
13 yield base;
14 }
15 default -> 0;
16 };
17
18 System.out.println("A " + ticket + " ticket costs " + price);
19 }
20}
Prints
›A student ticket costs 6

ticket is "student", which matches the second case. Its block starts base at 8, takes 2 off because tuesday is true, and yield base; hands 6 back as the value of the whole switch. The program prints A student ticket costs 6. Read yieldas “the value of this switch is”. A case that fits on one line, like case "adult" -> 12;, needs no yield.

This switch shows two more things. Its value is a String, and the cases compare text: case "child" matches any string that spells child, letter for letter. And one case lists two values, "child" and "student", separated by a comma.

Comparing two Strings in an if
A switch compares strings letter by letter, but == between two strings does not always do that. In an if, compare text with equals: if (ticket.equals("adult")). The strings lesson explains why == can say false for two strings with the same letters.
Quick check
Which statement about the arrow form of switch is true?
ExerciseDaysInMonth.java
Finish the program so it prints how many days a month has in a year that is not a leap year. February (month 2) has 28 days; April, June, September and November (4, 6, 9 and 11) have 30; every other month has 31. Replace the 0 with a switch expression on month. Each test types a different month on the keyboard.
Switches that ask what kind of thing they hold
A case can do more than match a fixed value. With patterns, a switch can check what kind of object it was given and take it apart, as in case Circle c -> .... That needs classes and records first, so it comes much later, in sealed types and pattern matching.
else if chain
switch
Tests any boolean condition: ranges, &&, ||, anything.
Tests one value against a list of exact values.
Great for thresholds like score >= 90.
Great for a fixed menu of cases like 1, 2, 3 or "small", "large".
Repeats the variable on each line.
Names the value once at the top.
Stops at the first true branch automatically.
Arrow form: runs one case. Colon form: falls through until a break.
For choosing between two values, the ternary is its short form.
A switch expression chooses a value from many cases.
Where this leads
Right now a decision runs at most once. The next step is repeating work while a condition stays true, which is what loops are for. A loop is, at heart, an if that gets asked again and again. Everything you learned about boolean conditions here carries straight over.

The shape of a decision, in one place#

  • if (cond) { } runs a block when cond is a boolean that is true. Always write the braces.
  • else handles the false case; else if adds more conditions, checked top to bottom, first match wins.
  • A condition is any expression yielding a boolean, so &&, ||, and ! all fit inside it.
  • cond ? a : b chooses between two values.
  • A colon switch jumps to the matching case and falls through until a break; default is the catch-all.
  • An arrow switch runs only the matching case. As a switch expression it produces a value, and yield gives the value of a case written as a block.
  • Write == to compare and = to assign. Mixing them up is the bug to watch for.

Try it

Answer each one in your head or on paper first, then open the answer.

1
What type must the expression inside if (...) evaluate to, and why is if (temperature) for an int temperature an error in Java?
2
In the grade example, the chain checks score >= 90, then >= 80, then >= 70, and so on. What grade does a score of 95 get if you reorder the chain to check score >= 60 first?
3
Rewrite this as a single ternary: String label; if (n % 2 == 0) label = "even"; else label = "odd";
4
In the day-of-week switch, suppose the break after case 1 is deleted and day is 1. What gets printed, and what is this behavior called?
5
Why does if (x = 5) fail to compile, while if (done = true) compiles when done is a boolean?
6
Rewrite the Feedback switch, the one that stacks 'A' and 'B', as a switch expression that stores the message in a String called message and prints it once.

Practice

Work through these on your own. Each one builds on this page.

1
In the playground, write a program that reads an int hour from 0 to 23 from the keyboard, with a Scanner as in the grade example, and prints "Good morning", "Good afternoon", or "Good evening" using an if / else if / else chain. Decide your own boundaries and type the value sitting exactly on each one.
2
Write a single ternary expression that stores into int larger whichever of two int variables a and b is greater. Then print it. Confirm it works when a is bigger, when b is bigger, and when they are equal.
3
Write a switch expression that turns an int month from 1 to 12 into its season in the northern hemisphere (12, 1 and 2 are winter, 3 to 5 spring, 6 to 8 summer, 9 to 11 autumn) and prints it. List each season's months in a single case label. Then write the same program with the colon form, stacked labels and break, and compare the two. Test months 3 and 12 in both.
4
Take the discount example and add one more case in plain words first, then in code: a person under 18 who is a member should get "Junior member discount". Where in the chain must this branch go so it is reached, and why? Write one sentence explaining the ordering before you change the code.
5
Build a small calculator. Read two whole numbers a and b, then a number from 1 to 4 that picks the operation: 1 adds, 2 subtracts, 3 multiplies and 4 divides. Use a switch expression to compute the result and print it. What should happen when the choice is 4 and b is 0? Handle that case with an if before the switch.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email