Build a Console Task Manager in Java
Build a menu-driven Java app that manages tasks with OOP and error handling.
Introduction
30 Second Summary
Everyone has a to-do list, whether it lives in an app, on paper, or just in your head. Building one from scratch is one of the best ways to learn how a programming language actually works.
In this project, you will build a console-based Task Manager application in Java. You will create a menu-driven program that adds, views, completes, and removes tasks from a list.
What You'll Build
Run your finished Task Manager in the terminal, type in tasks, mark them as done, and watch the list update with each action.
By the end of this project, you'll have:
- A menu-driven console app where you pick an action by number and see your task list update instantly in the terminal.
- A custom Task class you built because plain strings could not track completion. Each task displays [Pending] or [Done] next to its description.
- Robust error handling that catches bad input and responds with clear messages instead of crashing your program.
- Secret Mission: Add file persistence so your tasks survive between sessions.
Are there any prerequisites?
This project assumes you have some programming experience in another language. You should also have IntelliJ IDEA and a JDK (version 17 or later) already installed on your machine.
Before We Start
Before you write any code, take a moment to lock in what you are about to build and why it matters.
Set Up the Project and Run Your First Java Program
Before you can build anything, you need a working project. This step confirms that your JDK and IntelliJ IDEA are wired together correctly.
You will create a new Java project and write a starter class. Then you will run it to get your first visible output in the console.
In this step, get ready to:
- Create a new Java project in IntelliJ IDEA.
- Write and run your first Java class.
Create a new Java project in IntelliJ IDEA
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type IntelliJ IDEA and press Enter to open it.
You will land on the Welcome screen. From here, you will configure a new project.
- Click New Project.
- Select Java from the list on the left.
- Enter TaskManager in the Name field.
- Set Build system to IntelliJ.
- Select your installed JDK from the JDK dropdown.
- Make sure Add sample code is unchecked.
- Click Create.
IntelliJ IDEA opens your new project. You should see a src folder in the Project tool window on the left side.
Already have a project open?
If you do not see the Welcome screen, go to File | New | Project instead to open the New Project wizard.
Don't see the Project tool window?
Press Alt+1 to toggle the Project tool window. It may be collapsed or hidden by default.
Help me find the Project tool window in IntelliJ IDEA.
Create the main class
Every Java program needs a class with a main method. This is the entry point where your program starts running.
- In the Project tool window, right-click the src folder.
- Select New | Java Class.
- Type TaskManager and press Enter.
IntelliJ creates a TaskManager.java file with an empty class. You need to replace this with a starter program.
- Replace all the code in TaskManager.java with the following:
public class TaskManager {
// Entry point for the Task Manager program
public static void main(String[] args) {
System.out.println("=== Task Manager ===");
}
}
What does this code do?
- public class TaskManager defines a class named TaskManager. In Java, every program lives inside a class.
- public static void main(String[] args) is Java's standard entry point. Every Java program starts running from this method.
- System.out.println() prints a line of text to the console and adds a newline at the end.
✔️ Awesome, I've got everything!
You are all set. Make sure you have saved TaskManager.java.
ⓧ I'd like to double check the full code
Here is the complete TaskManager.java file so far:
public class TaskManager {
public static void main(String[] args) {
System.out.println("=== Task Manager ===");
}
}
Run the program
Before you run this, what do you expect to see in the console output?
- Click the green arrow in the gutter next to the main method.
- Select Run from the menu that appears.
The Run tool window opens at the bottom of IntelliJ IDEA. You should see === Task Manager === followed by Process finished with exit code 0.
What does exit code 0 mean?
An exit code of 0 means the program finished successfully with no errors. Any other number would indicate something went wrong.
Don't see the green arrow?
Check your code for red squiggly underlines. A red underline means a syntax error that prevents IntelliJ from recognizing the class as runnable.
Make sure the class is named TaskManager to match the filename TaskManager.java. In Java, the public class name must match the filename exactly.
Help me figure out why I cannot run my Java program in IntelliJ IDEA.
Your Java project is set up and your first class is running. Next, you will build out the Task Manager with a menu, user input, and the ability to add, view, and remove tasks.
Build a Working Task Manager with Strings
Your project compiles and prints a title. That is a great start, but a program that immediately exits is not very useful.
In this step, you will turn that static printout into an interactive menu. You will use Scanner to read user input and ArrayList to store tasks as simple strings.
In this step, get ready to:
- Build a menu loop that displays options and reads the user's choice.
- Implement add, view, and remove actions for string-based tasks.
- Test the full interactive flow from the console.
Add imports and create the menu loop
Your program needs two things: a way to read what the user types, and a place to store their tasks.
- At the very top of TaskManager.java, above public class TaskManager, add these two imports:
import java.util.ArrayList;
import java.util.Scanner;
- Inside the class, right below the opening brace of public class TaskManager { and above the main method, add these two fields:
// Store tasks in a resizable list
private static ArrayList<String> tasks = new ArrayList<>();
// Read user input from the console
private static Scanner scanner = new Scanner(System.in);
- In the main method, add all of this code below the existing System.out.println("=== Task Manager ==="); line:
boolean running = true;
// Keep showing the menu until the user exits
while (running) {
printMenu();
// Read the user's menu choice
int choice = scanner.nextInt();
scanner.nextLine(); // Consume the leftover newline
switch (choice) {
case 1:
addTask();
break;
case 2:
viewTasks();
break;
case 3:
removeTask();
break;
case 4:
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid choice.");
}
}
scanner.close();
What does this code do?
- boolean running controls whether the loop continues. Setting it to false (case 4) ends the program.
- scanner.nextInt() reads the number the user types at the menu prompt.
- scanner.nextLine() right after nextInt() clears the newline character left behind when the user presses Enter. Without this, the next nextLine() call would read an empty string instead of waiting for input.
- The switch statement routes each menu choice to its matching method.
- scanner.close() releases the input reader after the loop ends.
- Add the printMenu method and three placeholder methods after the closing } of main, but still inside the class braces:
// Display the menu options to the user
private static void printMenu() {
System.out.println("\n--- Menu ---");
System.out.println("1. Add Task");
System.out.println("2. View Tasks");
System.out.println("3. Remove Task");
System.out.println("4. Exit");
System.out.print("Enter your choice: ");
}
// Placeholder methods - you will fill these in next
private static void addTask() { }
private static void viewTasks() { }
private static void removeTask() { }
Why the empty methods?
printMenu() prints a numbered list of options each time the loop repeats.
The three empty methods (addTask, viewTasks, removeTask) are placeholders. Java requires them to exist because main already calls them. You will fill them in next.
- Save TaskManager.java.
- Click the green Run button next to main to start the program.
- Type 4 and press Enter to exit.
You should see the menu with four numbered options printed below === Task Manager ===, followed by Goodbye! when you choose option 4.
Program won't compile?
- Check that both import lines are above public class TaskManager, not inside it.
- Make sure all four methods (printMenu, addTask, viewTasks, removeTask) exist inside the class, even if three of them are still empty.
- Look for missing semicolons or braces. IntelliJ highlights syntax errors with a red underline.
- Help me fix a compilation error in my Java Task Manager.
Implement the menu actions
The menu is running and responding to input. Now you will fill in each placeholder method so the options actually do something.
- In TaskManager.java, find the line private static void addTask() { } and replace it with this full implementation:
private static void addTask() {
System.out.print("Enter task description: ");
// Read the full line of text the user types
String description = scanner.nextLine();
tasks.add(description);
System.out.println("Task added: " + description);
}
What does addTask do?
- scanner.nextLine() reads everything the user types until they press Enter.
- tasks.add(description) appends the new string to the end of the list.
- Save TaskManager.java.
- Run the program.
- Type 1 and press Enter.
- Type Buy groceries and press Enter.
You should see Task added: Buy groceries printed in the Run tool window. Type 4 to exit.
- Find the line private static void viewTasks() { } and replace it with this implementation:
private static void viewTasks() {
if (tasks.isEmpty()) {
System.out.println("No tasks yet.");
return;
}
System.out.println("\nYour Tasks:");
// Print each task with its 1-based number
for (int i = 0; i < tasks.size(); i++) {
System.out.println((i + 1) + ". " + tasks.get(i));
}
}
What does viewTasks do?
- tasks.isEmpty() checks whether the list has any items. If not, it prints a message and returns early.
- The for loop prints each task with a number starting from 1. Since i starts at 0, the display uses i + 1.
- Save TaskManager.java.
- Run the program.
- Type 1 and press Enter to add a task.
- Enter a description like Walk the dog and press Enter.
- Type 2 and press Enter to view tasks.
You should see a numbered list with 1. Walk the dog in the output. Type 4 to exit.
- Find the line private static void removeTask() { } and replace it with this implementation:
private static void removeTask() {
if (tasks.isEmpty()) {
System.out.println("No tasks to remove.");
return;
}
viewTasks();
System.out.print("Enter task number to remove: ");
// Read the task number to remove
int index = scanner.nextInt();
scanner.nextLine();
if (index < 1 || index > tasks.size()) {
System.out.println("Invalid task number.");
return;
}
String removed = tasks.remove(index - 1);
System.out.println("Removed: " + removed);
}
What does removeTask do?
- viewTasks() is called first so the user can see which task number to pick.
- The index validation (index < 1 || index > tasks.size()) prevents the user from entering an out-of-range number.
- tasks.remove(index - 1) converts the user's 1-based choice to the 0-based position that ArrayList uses internally.
- Save TaskManager.java.
- Run the program.
- Type 1 and press Enter to add a task.
- Enter a description and press Enter.
- Type 3 and press Enter to remove a task.
- Type 1 and press Enter to select the first task.
You should see Removed: followed by your task description. Type 4 to exit.
Task actions not working?
- If adding a task seems to skip your input, check that scanner.nextLine() appears right after every scanner.nextInt() call. This clears the leftover newline from pressing Enter.
- If removing a task crashes, make sure the if (index < 1 || index > tasks.size()) check comes before the tasks.remove() call.
- Help me debug my Java Task Manager menu actions.
Run and test the full loop
Before you test, predict: if you add two tasks, remove the first one, and then view your list, how many tasks will remain?
- Run the program.
- Type 1 and enter Buy groceries as the first task.
- Type 1 again and enter Clean kitchen as the second task.
- Type 2 to view both tasks in the numbered list.
- Type 3 to remove a task, then enter 1 to remove Buy groceries.
- Type 2 again to view the remaining tasks.
- Type 4 to exit.
One task should remain: 1. Clean kitchen, now renumbered as the first entry. The program exits cleanly with Goodbye!.
✔️ Awesome, I've got everything!
Make sure you have saved TaskManager.java.
ⓧ I'd like to double check the full code
Scroll through your TaskManager.java and confirm these sections appear in this order:
- Two import lines at the very top (ArrayList and Scanner).
- Two private static fields: tasks and scanner.
- main method with the while loop, switch statement, and scanner.close().
- printMenu() method with six print statements.
- addTask() method that reads input and adds to the list.
- viewTasks() method with the isEmpty check and for loop.
- removeTask() method with index validation and tasks.remove().
Your Task Manager handles adding, viewing, and removing tasks. Next, you will try adding a new feature and discover why plain strings are not enough.
Refactor to a Task Class When Strings Fall Short
Your Task Manager can add, view, and remove tasks. But right now, every task is just a plain String. What happens when you want to track whether a task is done?
In this step, you will hit a real limitation of storing tasks as strings. That limitation forces a refactor into a proper Java class, which is exactly how object-oriented programming earns its place in your code.
In this step, get ready to:
- Create a Task class that bundles a description and completion status together.
- Refactor TaskManager to use Task objects instead of raw strings.
- Add a "Mark Task Completed" feature and test completion tracking.
Hit the wall and create the Task class
Imagine you want to add a "Mark Completed" option to your menu. How would you track whether each task is done using your current ArrayList<String>?
You would need a second ArrayList<Boolean> running in parallel, keeping the same index in sync across both lists. Add a task? Update both lists. Remove a task? Remove from both. The moment those two lists fall out of sync, you have invisible bugs.
Why not just use parallel lists?
Parallel lists are fragile because nothing in the code forces them to stay aligned. If you accidentally remove from one list but not the other, every task after that index points to the wrong status. A class solves this by bundling the description and its completion status into a single object.
This is the moment where Java's class system stops being academic. You need it to solve a real problem.
- In the Project tool window (Alt+1), right-click the src folder.
- Select New, then Java Class.
- Name it Task and press Enter.
- Replace the generated code in Task.java with the class skeleton below:
public class Task {
// Store the task description
private String description;
// Track whether this task is completed
private boolean completed;
// Constructor takes a description and defaults to not completed
public Task(String description) {
this.description = description;
this.completed = false;
}
// Return the task description
public String getDescription() {
return description;
}
// Return whether the task is completed
public boolean isCompleted() {
return completed;
}
}
What does this code do?
The private keyword on description and completed means no code outside this class can change them directly. Other classes must go through the getter methods to read these values.
The constructor takes a String description and sets completed to false automatically. Every new task starts as pending.
Now you need two more methods: one to mark a task as completed, and one to control how the task looks when printed.
- Add the following methods inside your Task class, just before the closing }:
// Mark this task as completed
public void markCompleted() {
this.completed = true;
}
// Format task for display with status indicator
@Override
public String toString() {
String status = completed ? "Done" : "Pending";
return "[" + status + "] " + description;
}
What does toString() do?
The @Override annotation tells Java you are replacing the default toString() method that every object inherits. Without this override, printing a Task would show something like Task@3a71f4dd instead of a human-readable string.
Now when your code prints a Task object, Java automatically calls this method. A pending task appears as [Pending] Buy groceries and a completed task appears as [Done] Buy groceries.
- Save Task.java.
✔️ Awesome, I've got everything!
Great. Make sure you have saved Task.java before moving on.
ⓧ I'd like to double check the full code
Here is the complete Task.java file for comparison:
public class Task {
private String description;
private boolean completed;
public Task(String description) {
this.description = description;
this.completed = false;
}
public String getDescription() {
return description;
}
public boolean isCompleted() {
return completed;
}
public void markCompleted() {
this.completed = true;
}
@Override
public String toString() {
String status = completed ? "Done" : "Pending";
return "[" + status + "] " + description;
}
}
Seeing a red underline in Task.java?
- Make sure the class name Task matches the file name Task.java exactly. Java requires these to match, including the capital T.
- Check that every opening brace { has a matching closing brace }. The class has one pair, the constructor has one pair, and each method has one pair.
- If you see errors about @Override, make sure the toString() method signature is spelled exactly as shown, with a lowercase t.
Help me fix errors in my Task.java class.
Refactor TaskManager to use the Task class
With the Task class ready, you need to update TaskManager.java to use Task objects instead of raw strings. Three existing lines need to change.
- Switch to TaskManager.java in the editor.
- Find this line near the top of the class:
private static ArrayList<String> tasks = new ArrayList<>();
- Replace ArrayList<String> with ArrayList<Task>, so the line now looks like this:
private static ArrayList<Task> tasks = new ArrayList<>();
- Scroll down to the addTask() method. Find this line:
tasks.add(description);
- Replace it with new Task(description) so you are adding a Task object instead of a raw string:
tasks.add(new Task(description));
- Scroll down to the removeTask() method. Find these two lines:
String removed = tasks.remove(index - 1);
System.out.println("Removed: " + removed);
- Replace them so the variable type is Task and you call getDescription() to print the name:
Task removed = tasks.remove(index - 1);
System.out.println("Removed: " + removed.getDescription());
Why don't you need to change viewTasks()?
The viewTasks() method prints tasks.get(i) inside a println call. When Java prints an object, it automatically calls that object's toString() method. Since you just wrote a custom toString() in Task, the output now includes the [Pending] or [Done] status for free.
Before you test the full refactor, what do you think you will see next to your task descriptions now that they are Task objects instead of plain strings?
- Save TaskManager.java.
- Click the green arrow next to main and select Run.
- Choose option 1 to add a task (e.g. Buy groceries).
- Choose option 2 to view tasks.
You should see 1. [Pending] Buy groceries in the output. The [Pending] prefix comes from your toString() method doing its job.
- Choose option 4 to exit the program.
Seeing a compilation error?
- If IntelliJ shows a red underline on new Task(description), check that Task.java is saved and located in the same src folder as TaskManager.java.
- Make sure you changed ArrayList<String> to ArrayList<Task> on the field declaration. If the list still expects strings, adding a Task object will not compile.
Help me fix my Task class refactoring in Java.
The refactor works. Now it is time to add the feature that motivated the whole change: marking tasks as completed.
- Add the markTaskCompleted() method to TaskManager.java by pasting the following code after the viewTasks() method:
private static void markTaskCompleted() {
// Check if there are any tasks to mark
if (tasks.isEmpty()) {
System.out.println("No tasks to mark.");
return;
}
// Show current tasks so the user can pick one
viewTasks();
System.out.print("Enter task number to mark completed: ");
int index = scanner.nextInt();
scanner.nextLine();
// Validate the task number is in range
if (index < 1 || index > tasks.size()) {
System.out.println("Invalid task number.");
return;
}
// Mark the selected task and confirm
tasks.get(index - 1).markCompleted();
System.out.println("Task marked as completed.");
}
What does this method do?
- It first checks if the list is empty and returns early if there is nothing to mark.
- It shows the current tasks so the user can see the numbers, then reads their choice.
- It validates the number is within range, then calls markCompleted() on the selected Task object. This is the payoff of bundling data and behaviour together in a class.
The method exists, but the menu does not offer it yet. You need to update printMenu() and the switch statement to include the new option and renumber the rest.
- In the printMenu() method, find these two lines:
System.out.println("3. Remove Task");
System.out.println("4. Exit");
- Replace them with three lines that add the new option and renumber Remove and Exit:
System.out.println("3. Mark Task Completed");
System.out.println("4. Remove Task");
System.out.println("5. Exit");
- In the switch statement inside main, find the cases starting from case 3:
case 3:
removeTask();
break;
case 4:
running = false;
System.out.println("Goodbye!");
break;
- Replace them with the renumbered cases that include markTaskCompleted():
case 3:
markTaskCompleted();
break;
case 4:
removeTask();
break;
case 5:
running = false;
System.out.println("Goodbye!");
break;
- Save TaskManager.java.
✔️ Awesome, I've got everything!
Great. Confirm you have saved both Task.java and TaskManager.java before running.
ⓧ I'd like to double check the full code
TaskManager.java is too long to show in one block. Scroll up through your file and confirm each section is present, in this order:
- The import lines for ArrayList and Scanner at the top.
- The field declaration using ArrayList<Task> (not ArrayList<String>).
- The main method with cases 1 through 5 in the switch statement (case 3 calls markTaskCompleted(), case 4 calls removeTask(), case 5 exits).
- printMenu() listing 5 options, with option 3 as "Mark Task Completed".
- addTask() using new Task(description).
- viewTasks() unchanged from before.
- The new markTaskCompleted() method.
- removeTask() using Task removed and removed.getDescription().
Run and test completion tracking
Before you run, think about what will happen when you mark a task as completed. What should change in the view?
- Click the green arrow next to main and select Run.
- Choose option 1 and add a task (e.g. Buy groceries).
- Choose option 1 again and add a second task (e.g. Read a book).
- Choose option 2 to view tasks. Both should show [Pending].
- Choose option 3 and enter 1 to mark the first task as completed.
- Choose option 2 again to view tasks.
The first task now shows [Done] Buy groceries while the second still shows [Pending] Read a book. Each Task object tracks its own status independently.
Why this would have been painful with parallel lists
If you had used a second ArrayList<Boolean> for completion status, every add, remove, and mark operation would have needed to update two lists in lockstep. One missed update and task 3's status silently refers to task 4's data.
The Task class eliminates that risk. Data and the operations on that data live in one place. This is the core idea behind object-oriented programming.
- Choose option 5 to exit the program.
Task shows [Pending] after marking it completed?
- Check that markCompleted() in Task.java sets this.completed = true. A missing assignment means the field stays false forever.
- In markTaskCompleted() in TaskManager.java, confirm the line reads tasks.get(index - 1).markCompleted(). The - 1 is essential because the user enters 1-based numbers but the list uses 0-based indexes.
Help me debug why marking a task completed is not working.
Your Task Manager now tracks completion status cleanly using a real Java class. There is one problem left: if someone types abc at the menu prompt, the whole program crashes. Next up, you will make your app bulletproof with error handling.
Add Input Validation and Error Handling
Your Task Manager can add, view, complete, and remove tasks. But it has a hidden weakness that would embarrass any app in the real world.
If a user types letters where the menu expects a number, the entire program crashes. In this step, you will add error handling with Java's try-catch mechanism so your app handles bad input gracefully instead of blowing up.
In this step, get ready to:
- Wrap menu input in a try-catch block to handle non-numeric input.
- Add validation to task actions so empty descriptions and bad numbers are rejected gracefully.
- Test the application against every kind of bad input.
Wrap menu input in a try-catch
Right now, your program uses scanner.nextInt() to read the menu choice. This method expects a number. What happens when someone types something that is not a number?
Before you try this, what do you think will happen if you type letters instead of a number at the menu prompt?
- Run your program by clicking the green arrow next to main in the gutter.
- When the menu appears, type abc and press Enter.
The program crashes with an InputMismatchException. This error means Scanner received text when it expected an integer. That is exactly the problem you will fix now.
- Add this import at the top of TaskManager.java, after the existing import lines:
import java.util.InputMismatchException;
- Below the printMenu() method, add this new getMenuChoice() method:
private static int getMenuChoice() {
// Try to read an integer from the user
try {
int choice = scanner.nextInt();
scanner.nextLine(); // consume the leftover newline
return choice;
} catch (InputMismatchException e) {
// Clear the bad input so the scanner can keep reading
scanner.nextLine();
return -1; // signal an invalid choice
}
}
What does this code do?
The try block attempts to read an integer. If the user types a valid number, the method returns it normally.
If the user types something that is not a number, Java throws an InputMismatchException. The catch block intercepts that exception, clears the bad input with scanner.nextLine(), and returns -1 so the menu loop can show an error message instead of crashing.
Now you need to update the main method to use your new getMenuChoice() method instead of calling scanner.nextInt() directly.
- In the main method inside the while loop, find these two lines:
int choice = scanner.nextInt();
scanner.nextLine();
- Replace both lines with this single line:
int choice = getMenuChoice();
- In the same switch statement, find the default case:
System.out.println("Invalid choice.");
- Replace it with this line so the message tells users exactly what to type:
System.out.println("Invalid choice. Please enter 1-5.");
- Save TaskManager.java.
- Run the program again. When the menu appears, type abc and press Enter.
Instead of crashing, the program prints Invalid choice. Please enter 1-5. and shows the menu again. The try-catch caught the exception before it could crash the program.
Still seeing a crash?
- Make sure you deleted both the scanner.nextInt() and scanner.nextLine() lines in main and replaced them with the single getMenuChoice() call.
- Check that import java.util.InputMismatchException; appears at the top of the file, after the other imports.
- Confirm your catch block catches InputMismatchException, not a generic Exception.
Why is my program still crashing on bad input?
Add validation to task actions
The menu is safe, but the individual actions still have the same problem. Typing letters when removeTask() asks for a task number will crash the program. Empty task descriptions should also be rejected.
- In TaskManager.java, find the addTask() method and replace it with this updated version:
private static void addTask() {
System.out.print("Enter task description: ");
// Trim whitespace so blank entries are caught
String description = scanner.nextLine().trim();
if (description.isEmpty()) {
System.out.println("Task description cannot be empty.");
return;
}
tasks.add(new Task(description));
System.out.println("Task added: " + description);
}
What changed here?
- .trim() strips leading and trailing whitespace from the input. Without it, a user could enter just spaces and create a blank task.
- .isEmpty() checks whether the trimmed string has zero characters. If it does, the method prints an error and returns early without adding anything to the list.
- Save TaskManager.java and run the program.
- Choose option 1 to add a task, then press Enter without typing a description.
You should see Task description cannot be empty. and the menu reappears. No blank task was added.
- Choose option 5 to exit the program.
Now apply the same try-catch pattern to the two methods that ask for a task number.
- Find the markTaskCompleted() method and replace it with this updated version:
private static void markTaskCompleted() {
if (tasks.isEmpty()) {
System.out.println("No tasks to mark.");
return;
}
viewTasks();
System.out.print("Enter task number to mark completed: ");
try {
int index = scanner.nextInt();
scanner.nextLine();
// Validate the number is within the list range
if (index < 1 || index > tasks.size()) {
System.out.println("Invalid task number.");
return;
}
tasks.get(index - 1).markCompleted();
System.out.println("Task marked as completed.");
} catch (InputMismatchException e) {
scanner.nextLine(); // clear the bad input
System.out.println("Please enter a valid number.");
}
}
What does the try-catch do here?
The try block wraps everything from reading the number through completing the task. If the user types letters instead of a number, the catch block clears the bad input and prints a friendly message.
The range check (index < 1 || index > tasks.size()) stays inside the try block because it only runs when a valid number was entered. This handles two separate problems: wrong data type and out-of-range values.
- Find the removeTask() method and replace it with this updated version:
private static void removeTask() {
if (tasks.isEmpty()) {
System.out.println("No tasks to remove.");
return;
}
viewTasks();
System.out.print("Enter task number to remove: ");
try {
int index = scanner.nextInt();
scanner.nextLine();
// Validate the number is within the list range
if (index < 1 || index > tasks.size()) {
System.out.println("Invalid task number.");
return;
}
Task removed = tasks.remove(index - 1);
System.out.println("Removed: " + removed.getDescription());
} catch (InputMismatchException e) {
scanner.nextLine(); // clear the bad input
System.out.println("Please enter a valid number.");
}
}
What does this code do?
This follows the exact same pattern as markTaskCompleted(). The try-catch wraps the number input and the range check. If the input is not a number, the catch block handles it. If the number is out of range, the if-check handles it.
- Save TaskManager.java.
Seeing a compile error?
- Make sure every opening try { has a matching } catch block. Mismatched braces are the most common cause of compile errors here.
- Check that InputMismatchException is imported at the top of the file. Without the import, Java does not recognize the exception type.
Help me fix a compile error in my TaskManager try-catch blocks.
✔️ Awesome, I've got everything!
Great. Make sure you have saved TaskManager.java before moving on to the final test.
ⓧ I'd like to double check the full code
Your TaskManager.java is too long to show in one block, so confirm each section is present and in order:
- Three imports at the top: ArrayList, Scanner, and InputMismatchException.
- The main method uses getMenuChoice() instead of scanner.nextInt(), and the default case prints "Invalid choice. Please enter 1-5.".
- printMenu() is unchanged from the previous step.
- getMenuChoice() wraps scanner.nextInt() in a try-catch and returns -1 on error.
- addTask() calls .trim() on the input and rejects empty descriptions.
- viewTasks() is unchanged.
- markTaskCompleted() wraps the index input in a try-catch for InputMismatchException.
- removeTask() wraps the index input in a try-catch for InputMismatchException.
Task.java is unchanged in this step.
Test the bulletproof version
Every input path is now protected. Time to throw everything you can at the program and make sure nothing breaks it.
Before you run this, do you think any of these inputs will crash the program? Think about which ones the try-catch blocks will handle and which ones the if-checks will handle.
- Run the program.
- At the menu prompt, type abc and press Enter. You should see Invalid choice. Please enter 1-5. and the menu reappears.
- Choose option 1 and add a task called Buy groceries.
- Choose option 1 again and add a second task called Walk the dog.
- Choose option 4 to remove a task. Enter 99 as the task number. You should see Invalid task number.
- Choose option 1 to add a task. Press Enter without typing anything. You should see Task description cannot be empty.
- Choose option 5 to exit cleanly.
Every single bad input produced a clear error message instead of a crash. The program recovered and showed the menu again each time.
Why does this matter?
Real-world applications never assume users will enter perfect input. The try-catch pattern you just used is how Java programs handle unexpected situations without crashing.
Tracking completion with parallel ArrayLists would have been a nightmare. The try-catch blocks would have been even worse to maintain across two lists. Your Task class keeps everything clean.
Secret mission
Save and Load Tasks from a File
Every time you close the Task Manager, all your tasks vanish. In this mission, you will add file persistence so your tasks survive between sessions. You will write tasks to a text file and load them back when the program starts.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files to continue building, or delete them entirely.
Resources you used:
- TaskManager project folder containing Task.java, TaskManager.java, and tasks.txt (if you completed the Secret Mission).
Keep everything running
No action needed. Choose this if you plan to keep building on the project or want to revisit the code later.
- Your TaskManager project stays in IntelliJ IDEA and appears in your recent projects list whenever you open the app.
- The tasks.txt file lives in your project root directory and persists your tasks between sessions.
Pause - I'll come back to this later
Close IntelliJ IDEA to free up system memory. Your project files stay on disk and are ready to reopen anytime.
- Close IntelliJ IDEA.
- When you are ready to continue, reopen IntelliJ IDEA and select TaskManager from the recent projects list on the Welcome screen.
Delete - I don't want to use this again
Remove the project from IntelliJ IDEA and delete the files from your computer.
- Close the TaskManager project if it is open. Go to File, then Close Project.
- On the Welcome screen, right-click TaskManager in the project list and choose Remove from Recent Projects.
- Open Finder (macOS) or File Explorer (Windows) and navigate to the folder where you created the project.
- Delete the TaskManager folder.
Nice Work!
Nice Work!
Nice work! You just built a complete console-based Task Manager application from scratch in Java. You went from printing a single line to running a fully interactive program that tracks tasks, handles bad input gracefully, and understands why classes exist.
You've learned how to:
- Build a menu-driven console application using Scanner for user input, ArrayList for dynamic storage, a while loop, and a switch statement to route choices.
- Create a custom Task class with private fields, a constructor, getter methods, and a toString() override. You learned why OOP exists by hitting a real limitation with plain strings first.
- Add robust input validation and error handling with try-catch blocks so the app handles bad input gracefully instead of crashing.
- Secret Mission: Added file persistence with BufferedWriter and BufferedReader, so tasks survive between sessions with their completion status preserved.
Ready to quiz yourself?