Build a Contact Book in Java
Use ArrayList, HashMap, Stack, and Queue to build a CLI contact book.
Introduction
30 Second Summary
Think about your phone's contact list. You can scroll through names in order, search instantly by name, undo a delete, and see your recent searches.
In this project, you will build a command-line Contact Book application in Java that uses four different data structures, each chosen for a specific job.
What You'll Build
A CLI Contact Book app that manages contacts using ArrayList, HashMap, ArrayDeque (as a Stack), and LinkedList (as a Queue) from the Java Collections Framework.
By the end of this project, you'll have:
- A working Contact Book application you can interact with in the terminal.
- Hands-on experience with four core data structures from the Java Collections Framework (ArrayList, HashMap, ArrayDeque, LinkedList).
- A clear mental model of when and why to choose each data structure based on the operation you need.
- Secret Mission: Add a "Favorites" feature using a LinkedHashSet that maintains insertion order and prevents duplicates.
Are there any prerequisites?
You should have a basic understanding of variables, loops, and conditionals in any programming language. No prior Java experience is required.
Before We Start
To build a Contact Book that uses multiple data structures, you need Java installed on your machine.
Java code goes through two stages: compilation (turning your code into something the computer understands) and execution (actually running it). In this step, you will install the tools for both stages and create your project folder.
In this step, get ready to:
- Install JDK 25 for compiling and running Java code.
- Create a project folder and verify your setup.
- Set up VS Code with an empty ContactBook.java file.
Download and install JDK 25
The Java Development Kit (JDK) includes two essential commands: javac for compiling your code and java for running it. JDK 25 is the latest Long-Term Support release, meaning it will receive updates for years.
- Open your browser and go to Oracle's JDK downloads page:
- https://www.oracle.com/java/technologies/downloads/
macOS
- Click the macOS tab on the downloads page.
- Click the ARM64 DMG Installer download link if you have an Apple Silicon Mac (M1, M2, M3, or M4 chip). Click the x64 DMG Installer if you have an older Intel Mac.
- Once downloaded, double-click the .dmg file to open it.
- Double-click the .pkg file inside the window that appears.
- Follow the installer prompts: click Continue, then Install.
- Enter your Mac password when prompted, then click Install Software.
Not sure which Mac you have?
Click the Apple menu in the top-left corner, then About This Mac. If the Chip line says M1, M2, M3, or M4, choose the ARM64 installer. If it says Intel, choose x64.
Windows
- Click the Windows tab on the downloads page.
- Click the x64 MSI Installer download link.
- Once downloaded, double-click the .msi file to launch the installer.
- Click Next through each screen, accepting the default settings.
- Click Close when the installation finishes.
What does the installer do?
The Windows installer adds java and javac to your system PATH automatically. This means you can run both commands from any terminal window without extra configuration.
Verify your Java installation
Now you will confirm that both the compiler and the runtime were installed correctly.
- Open a terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Check the Java runtime version by running this command:
java --version
- Check the Java compiler version by running this command:
javac --version
✔️ I see version 25
Both commands show version 25 in the output. You are ready to move on.
ⓧ I see an older version
You have an older JDK installed. The JDK 25 installer you just ran should have updated your system. Try closing your terminal completely, opening a fresh one, and running the commands again.
If you still see an older version, your system may be pointing to the old installation. On macOS, the new JDK installs to /Library/Java/JavaVirtualMachines/ and should take priority automatically. On Windows, the installer updates your PATH, but you may need to restart your computer for the change to take effect.
ⓧ Command not found
This means the JDK is not installed yet, or the installer did not finish. Go back to the previous substep and complete the download and installation for your operating system.
On Windows, if the installer completed but java is still not recognized, restart your computer. The PATH update sometimes requires a full restart to take effect.
Still not working?
Make sure you downloaded JDK 25 specifically from the downloads page, not JDK 26 or JDK 21. The page defaults to the latest version, so scroll down or look for the JDK 25 section.
help me troubleshoot why java --version is not working after installing JDK 25
Create the project folder and open VS Code
You need a dedicated folder to hold your Java file. You will also need a code editor to write in.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir ContactBook
cd ContactBook
- Confirm the folder was created by running:
ls
You should see an empty output since the folder is new and has no files yet.
Why VS Code?
VS Code is a free, lightweight code editor that gives you syntax highlighting and a built-in terminal. You can also use Cursor, which is built on VS Code and works the same way. Either one works for this project.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code (or Cursor) and press Enter to open it.
✔️ VS Code opened
VS Code is ready. Continue with the next instructions to open your project folder.
ⓧ I don't have VS Code installed
- Download VS Code from https://code.visualstudio.com.
- Run the installer and follow the default prompts.
- Once installed, open VS Code using the search bar instructions above.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the ContactBook folder, then click Open.
Trust the authors prompt
VS Code may ask if you trust the authors of the files in this folder. Click Yes, I trust the authors to continue.
- Create a new file by clicking the new file icon in the Explorer sidebar (the page icon with a plus sign), or press Cmd+N (macOS) or Ctrl+N (Windows).
- Name the file ContactBook.java and press Enter.
- Save the empty file by pressing Cmd+S (macOS) or Ctrl+S (Windows).
You should see ContactBook.java listed in the Explorer sidebar on the left.
Don't see the Explorer sidebar?
Press Cmd+Shift+E (macOS) or Ctrl+Shift+E (Windows) to toggle the Explorer panel. You should see your ContactBook folder name at the top with ContactBook.java inside it.
help me find my ContactBook.java file in VS Code
Your environment is ready. Next up, you will start writing Java code and learn how ArrayList and HashMap work together to store and retrieve contacts.
Set Up Java and Your Project
Your Java environment is ready and you have an empty ContactBook.java waiting to be filled. Every Java application starts with a class, a main method, and imports for the libraries it needs.
In this step, you'll write the foundational skeleton for your Contact Book. This skeleton declares the two core data structures you'll build on throughout the project: an ArrayList for ordered storage and a HashMap for fast lookups.
In this step, get ready to:
- Write the class skeleton with ArrayList and HashMap field declarations.
- Compile the file to confirm Java can build it successfully.
Write the ContactBook class skeleton
Java uses a class as the container for your entire program. Inside that class, you declare fields (the data your program holds) and methods (the actions your program can take).
- Switch to ContactBook.java in VS Code and paste the following code:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
public class ContactBook {
// Ordered list of contact names
static ArrayList<String> contactNames = new ArrayList<>();
// Maps each contact name to their phone number
static HashMap<String, String> phoneBook = new HashMap<>();
public static void main(String[] args) {
// Read user input from the terminal
Scanner scanner = new Scanner(System.in);
System.out.println("=== Contact Book ===");
scanner.close();
}
}
What does this code do?
- The three import lines bring in ArrayList, HashMap, and Scanner from Java's collections library.
- contactNames is an ArrayList that stores contact names in the order they were added.
- phoneBook is a HashMap that maps each name to a phone number for instant lookup.
- The main method is where Java starts running your program. Right now it just prints a title and exits.
What are generics?
The angle brackets in ArrayList<String> and HashMap<String, String> are called generics. They tell Java what type of data the collection holds.
Without generics, you could accidentally put a number where a name should go. Generics catch those mistakes at compile time instead of crashing at runtime.
- Save ContactBook.java.
- In the terminal, compile the file by running:
javac ContactBook.java
- If compilation succeeds, you'll see no output at all. That silence means everything compiled correctly.
- Now run the program to see your title banner:
java ContactBook
- You should see === Contact Book === printed in the terminal.
Seeing a compilation error?
- Check that your filename is exactly ContactBook.java with a capital C and capital B. Java requires the filename to match the class name.
- Make sure you have the closing } brace for both the main method and the class itself. Missing braces are the most common syntax error.
- Confirm your terminal is inside the ContactBook folder where the file lives. Run ls to check.
Why won't my Java file compile?
✔️ Awesome, I've got everything!
Great. Double check you've saved your file, then move on.
ⓧ I'd like to double check the full code
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
public class ContactBook {
static ArrayList<String> contactNames = new ArrayList<>();
static HashMap<String, String> phoneBook = new HashMap<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Contact Book ===");
scanner.close();
}
}
Your skeleton is ready and compiling cleanly. Next up, you'll bring this Contact Book to life by building the add and display features on top of these two data structures.
Build the ArrayList and HashMap Foundation
Your ContactBook.java already has two data structures ready to go: an ArrayList for storing names in order, and a HashMap for mapping names to phone numbers.
In this step, you'll build methods that put both data structures to work. Then you'll wire them into an interactive menu so you can run the app and see results.
In this step, get ready to:
- Build the addContact method to store names and numbers.
- Build the showAllContacts method to display contacts in order.
- Add an interactive menu loop and test the app with real contacts.
Build the addContact method
Each time a user adds a contact, the name goes into the ArrayList to preserve insertion order. The name-phone pair also goes into the HashMap for fast lookup later.
- In ContactBook.java, add the following method below your main method's closing brace:
static void addContact(Scanner scanner) {
// Ask for the contact's name
System.out.print("Enter name: ");
String name = scanner.nextLine();
// Ask for the contact's phone number
System.out.print("Enter phone number: ");
String phone = scanner.nextLine();
// Store in both data structures
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Contact added: " + name + " -> " + phone);
}
What does this code do?
- contactNames.add(name) appends the name to the end of the ArrayList. This preserves the order contacts were added.
- phoneBook.put(name, phone) stores the name-phone pair in the HashMap. Later, you can retrieve any phone number instantly by name.
- Using both structures together gives you ordered listing AND fast lookup. One data structure alone can't do both efficiently.
You won't see a change yet. The next chunk builds the display method, then we'll wire both into a menu you can interact with.
Build the showAllContacts method
The ArrayList keeps contacts in insertion order, which means you can iterate through it by index. For each name, you'll pull the matching phone number from the HashMap.
- Add the following method below your addContact method:
static void showAllContacts() {
// Handle empty list
if (contactNames.isEmpty()) {
System.out.println("No contacts yet.");
return;
}
System.out.println("\n--- All Contacts ---");
// Loop through ArrayList by index for numbered display
for (int i = 0; i < contactNames.size(); i++) {
String name = contactNames.get(i);
// Pull phone number from HashMap using the name as key
System.out.println((i + 1) + ". " + name + " -> " + phoneBook.get(name));
}
}
What does this code do?
- contactNames.isEmpty() checks if the list has zero entries before trying to loop through it.
- contactNames.get(i) retrieves the name at position i in constant time. This is one of ArrayList's strengths: fast indexed access.
- phoneBook.get(name) grabs the phone number for that name from the HashMap in O(1) average time.
- Save ContactBook.java.
- Compile your file to check for syntax errors by running:
javac ContactBook.java
You should see no output. No output from javac means your code compiled successfully.
Seeing a compilation error?
- Check that each method is placed outside the main method but inside the ContactBook class braces.
- Make sure you haven't accidentally deleted the class's final closing brace } at the very bottom of the file.
- Help me fix my ContactBook.java compilation error
Add the menu loop and test your app
Your methods exist, but there's no way to call them yet. You need a menu loop that repeatedly asks for user input and calls the right method based on their choice.
- Find your main method in ContactBook.java. It currently looks like this:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Contact Book ===");
scanner.close();
}
- Replace the entire main method with the following code:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Contact Book ===");
// Keep showing the menu until the user chooses to exit
while (running) {
System.out.println("\n1. Add Contact");
System.out.println("5. Show All Contacts");
System.out.println("7. Exit");
System.out.print("Choose an option: ");
String choice = scanner.nextLine();
// Route the user's choice to the right method
switch (choice) {
case "1":
addContact(scanner);
break;
case "5":
showAllContacts();
break;
case "7":
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid option. Try again.");
}
}
scanner.close();
}
What does this code do?
- boolean running = true controls the loop. When the user picks option 7, it flips to false and the loop ends.
- The switch statement routes user input to the correct method. Options 2, 3, 4, and 6 will be added in later steps.
- scanner.close() runs only after the loop exits, releasing the input resource cleanly.
Why do the menu numbers skip from 1 to 5 to 7?
You'll add options 2, 3, 4, and 6 in the upcoming steps as you build search, delete, undo, and recent-searches features. The numbers are already assigned so the menu stays consistent throughout the project.
- Save ContactBook.java.
- Compile and run your app by running these commands:
javac ContactBook.java
java ContactBook
You should see === Contact Book === followed by the menu options.
- Type 1 and press Enter to add a contact.
- Enter Alice as the name and 555-0101 as the phone number.
- Add two more contacts: Bob with 555-0102, and Charlie with 555-0103.
- Type 5 and press Enter to show all contacts.
You should see all three contacts listed in the order you added them, with numbered positions and phone numbers displayed.
- Type 7 and press Enter to exit the app.
App not running or showing errors?
- If you see a compilation error, make sure the addContact and showAllContacts methods are placed after the main method but still inside the class.
- If the menu appears but input doesn't work, check that your switch cases use strings ("1") not integers (1). The scanner reads input as a String.
- Make sure your terminal is in the ContactBook folder when you run the commands.
- Help me debug my ContactBook app
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here's the complete ContactBook.java file so far. Make sure yours matches exactly:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
public class ContactBook {
static ArrayList<String> contactNames = new ArrayList<>();
static HashMap<String, String> phoneBook = new HashMap<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Contact Book ===");
while (running) {
System.out.println("\n1. Add Contact");
System.out.println("5. Show All Contacts");
System.out.println("7. Exit");
System.out.print("Choose an option: ");
String choice = scanner.nextLine();
switch (choice) {
case "1":
addContact(scanner);
break;
case "5":
showAllContacts();
break;
case "7":
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid option. Try again.");
}
}
scanner.close();
}
static void addContact(Scanner scanner) {
System.out.print("Enter name: ");
String name = scanner.nextLine();
System.out.print("Enter phone number: ");
String phone = scanner.nextLine();
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Contact added: " + name + " -> " + phone);
}
static void showAllContacts() {
if (contactNames.isEmpty()) {
System.out.println("No contacts yet.");
return;
}
System.out.println("\n--- All Contacts ---");
for (int i = 0; i < contactNames.size(); i++) {
String name = contactNames.get(i);
System.out.println((i + 1) + ". " + name + " -> " + phoneBook.get(name));
}
}
}
Your Contact Book can store and display contacts. Next up, you'll add HashMap-powered search so you can find any contact instantly by name.
Add HashMap-Powered Search
Your Contact Book can add and display contacts. Now you need a way to find a specific contact without scrolling through the entire list.
This is where the HashMap earns its place. Instead of scanning every contact one by one (like you'd have to with an ArrayList), the HashMap can jump straight to the answer in constant time. In this step, you'll build a search method that proves this speed advantage.
In this step, get ready to:
- Build a searchContact method powered by HashMap's O(1) lookup.
- Wire up the search option in the menu and test it with real contacts.
Build the searchContact method
Searching through an ArrayList means checking every single element until you find a match. That's O(n) time, meaning it gets slower the more contacts you have. The HashMap's containsKey() method gives you O(1) average-time lookup, finding the answer in the same amount of time whether you have 5 contacts or 5,000.
The search method will also track what you've searched for recently using a LinkedList. You'll display that search history in a later step.
- In ContactBook.java, find the import lines at the top of the file. Add this new import below import java.util.HashMap;:
import java.util.LinkedList;
- Find the line that says static HashMap<String, String> phoneBook = new HashMap<>(); and add these two lines directly below it:
static LinkedList<String> recentSearches = new LinkedList<>();
static final int MAX_RECENT = 5;
- Add the searchContact method below the closing brace of the addContact method by copying the following code:
static void searchContact(Scanner scanner) {
System.out.print("Enter name to search: ");
String name = scanner.nextLine();
// Track this search in our recent searches queue
recentSearches.addLast(name);
if (recentSearches.size() > MAX_RECENT) {
recentSearches.removeFirst();
}
// Use HashMap for O(1) lookup instead of scanning the ArrayList
if (phoneBook.containsKey(name)) {
System.out.println("Found: " + name + " -> " + phoneBook.get(name));
} else {
System.out.println("Contact not found: " + name);
}
}
What does this code do?
This method handles two responsibilities every time you search:
- recentSearches.addLast(name) adds the search term to the back of a LinkedList queue. If the queue grows beyond 5 items, removeFirst() drops the oldest search. You'll display this history in a later step.
- phoneBook.containsKey(name) checks whether the name exists as a key in the HashMap. This is the O(1) lookup. No looping through the ArrayList required.
Why is HashMap faster than ArrayList for search?
An ArrayList stores items in order, so finding a specific name means checking each element one by one. With 1,000 contacts, you might check all 1,000 before finding a match.
A HashMap uses a hash function to calculate exactly where each key is stored. It jumps straight to the answer. Whether you have 10 contacts or 10,000, the lookup takes the same amount of time on average.
- Save ContactBook.java.
Seeing a red underline on recentSearches?
Make sure you added import java.util.LinkedList; at the top of the file. Without the import, Java doesn't know what LinkedList is.
Also check that the recentSearches and MAX_RECENT declarations are inside the class but outside any method.
Help me debug my searchContact method.
Wire up the search option in the menu
The method exists but the menu doesn't know about it yet. You need to add the display line and a switch case so users can actually trigger a search.
- In the main method, find the menu display section inside the while loop. Find this line:
System.out.println("\n1. Add Contact");
- Add this line directly below it:
System.out.println("2. Search Contact");
- Now find the switch statement below the menu. Find this case:
case "1":
addContact(scanner);
break;
- Add this new case directly below the break; of case "1":
case "2":
searchContact(scanner);
break;
- Save ContactBook.java.
- Compile and run the program by running these commands in your terminal:
javac ContactBook.java
java ContactBook
- First, add a few contacts using option 1 (try adding 2-3 names with phone numbers).
- Then choose option 2 and search for a name you just added. You should see a "Found:" message with the name and phone number.
- Search for a name that doesn't exist. You should see "Contact not found:" followed by the name you typed.
What should I see?
The menu now shows option 2. When you search for an existing contact, the result appears instantly. When you search for a name that doesn't exist, you get a clear "not found" message. The HashMap finds results in the same amount of time regardless of how many contacts you have.
Getting a compilation error?
Check that your case "2": is inside the switch block, not outside it. Each case must sit between the opening { after switch (choice) and the closing }.
Also verify that the searchContact method is inside the class but outside the main method. It should be at the same level as addContact.
Help me fix my compilation error.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here's the full ContactBook.java file at this point. Compare it against yours:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;
public class ContactBook {
static ArrayList<String> contactNames = new ArrayList<>();
static HashMap<String, String> phoneBook = new HashMap<>();
static LinkedList<String> recentSearches = new LinkedList<>();
static final int MAX_RECENT = 5;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Contact Book ===");
while (running) {
System.out.println("\n1. Add Contact");
System.out.println("2. Search Contact");
System.out.println("5. Show All Contacts");
System.out.println("7. Exit");
System.out.print("Choose an option: ");
String choice = scanner.nextLine();
switch (choice) {
case "1":
addContact(scanner);
break;
case "2":
searchContact(scanner);
break;
case "5":
showAllContacts();
break;
case "7":
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid option. Try again.");
}
}
scanner.close();
}
static void addContact(Scanner scanner) {
System.out.print("Enter name: ");
String name = scanner.nextLine();
System.out.print("Enter phone number: ");
String phone = scanner.nextLine();
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Contact added: " + name + " -> " + phone);
}
static void searchContact(Scanner scanner) {
System.out.print("Enter name to search: ");
String name = scanner.nextLine();
recentSearches.addLast(name);
if (recentSearches.size() > MAX_RECENT) {
recentSearches.removeFirst();
}
if (phoneBook.containsKey(name)) {
System.out.println("Found: " + name + " -> " + phoneBook.get(name));
} else {
System.out.println("Contact not found: " + name);
}
}
static void showAllContacts() {
if (contactNames.isEmpty()) {
System.out.println("No contacts yet.");
return;
}
System.out.println("\n--- All Contacts ---");
for (int i = 0; i < contactNames.size(); i++) {
String name = contactNames.get(i);
System.out.println((i + 1) + ". " + name + " -> " + phoneBook.get(name));
}
}
}
Your Contact Book now has instant search. Next up, you'll add the ability to delete contacts and undo those deletions with a stack.
Add Stack-Based Undo with ArrayDeque
Your Contact Book can add, search, and display contacts. But what happens when you accidentally delete someone?
In this step, you'll add delete functionality with a built-in safety net: an undo feature powered by a stack. A stack always gives you back the most recent item first, making it perfect for undoing your last action.
In this step, get ready to:
- Build a delete method that saves deleted contacts onto a stack before removing them.
- Build an undo method that restores the most recently deleted contact.
- Test LIFO (Last In, First Out) behavior by deleting and undoing multiple contacts.
Import ArrayDeque and build the delete method
ArrayDeque is Java's recommended class for stack behavior. It provides push() and pop() methods that add and remove items from the top of the stack.
- In ContactBook.java, find the import section at the top of the file. Add this import after import java.util.HashMap;:
import java.util.ArrayDeque;
- Now find the field declarations below public class ContactBook {. Add this line after static HashMap<String, String> phoneBook = new HashMap<>();:
static ArrayDeque<String> undoStack = new ArrayDeque<>();
- Add the deleteContact method below the searchContact method by copying this code:
static void deleteContact(Scanner scanner) {
// Ask the user which contact to delete
System.out.print("Enter name to delete: ");
String name = scanner.nextLine();
if (phoneBook.containsKey(name)) {
// Save the contact data to the undo stack before deleting
String phone = phoneBook.get(name);
undoStack.push(name + ":" + phone);
// Remove from both data structures
contactNames.remove(name);
phoneBook.remove(name);
System.out.println("Deleted: " + name);
System.out.println("(You can undo this action)");
} else {
System.out.println("Contact not found: " + name);
}
}
What does this code do?
- phoneBook.containsKey(name) checks if the contact exists before trying to delete it.
- undoStack.push(name + ":" + phone) saves the contact's data onto the stack BEFORE deleting. This is what makes undo possible.
- The method removes the name from both contactNames (the ArrayList) and phoneBook (the HashMap) to keep them in sync.
- Save ContactBook.java.
You won't see a change yet. The next substep adds the undo method, then we'll wire both into the menu.
Build the undo method
This is where LIFO (Last In, First Out) comes to life. The last contact you deleted sits on top of the stack, so it's the first one you can restore.
- Add the undoDelete method below the deleteContact method you just wrote:
static void undoDelete() {
// Check if there's anything to undo
if (undoStack.isEmpty()) {
System.out.println("Nothing to undo.");
return;
}
// Pop the most recently deleted contact from the stack
String entry = undoStack.pop();
String[] parts = entry.split(":", 2);
String name = parts[0];
String phone = parts[1];
// Restore the contact to both data structures
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Restored: " + name + " -> " + phone);
}
What does this code do?
- undoStack.pop() removes and returns the top item from the stack. This is always the most recently deleted contact.
- entry.split(":", 2) splits the stored string back into name and phone. The 2 limits the split to two parts in case the phone number contains a colon.
- The restored contact gets added back to both contactNames and phoneBook so everything stays in sync.
Why use ArrayDeque instead of the Stack class?
Java has a legacy Stack class, but the Java Collections Framework recommends ArrayDeque for stack behavior. ArrayDeque is faster because it is not synchronized, and it provides the same push() and pop() methods.
- Save ContactBook.java.
You won't see a change yet. The next substep wires both methods into the menu so you can test them.
Wire up delete and undo in the menu
Both methods are ready. Now you need to add menu options so the user can reach them.
- In the main method, find the menu display section. Locate this line:
System.out.println("5. Show All Contacts");
- Add these two lines directly above it (between the "2. Search Contact" line and the "5. Show All Contacts" line):
System.out.println("3. Delete Contact");
System.out.println("4. Undo Delete");
- Now find the switch statement. Locate this case:
case "5":
showAllContacts();
break;
- Add these two new cases directly above it (between the case "2" block and the case "5" block):
case "3":
deleteContact(scanner);
break;
case "4":
undoDelete();
break;
What does this code do?
These additions give the user two new menu choices. Option 3 calls deleteContact(scanner) which saves before removing. Option 4 calls undoDelete() which restores the most recently deleted contact.
- Save ContactBook.java.
- Compile your updated file by running:
javac ContactBook.java
- Run the app:
java ContactBook
- Add 3 contacts (option 1) with any names and phone numbers.
- Delete 2 of them (option 3).
- Undo (option 4) twice.
- Show all contacts (option 5) to confirm both were restored.
Notice the order of restoration. The second contact you deleted comes back first. That's LIFO in action.
Seeing a compilation error?
- Check that import java.util.ArrayDeque; is at the top of the file with the other imports.
- Make sure the undoStack field declaration is inside the class but outside any method.
- Verify your switch cases use case "3": and case "4": with quotes around the numbers (they're Strings, not ints).
Help me fix my ContactBook compilation error
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
import java.util.ArrayList;
import java.util.HashMap;
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Scanner;
public class ContactBook {
static ArrayList<String> contactNames = new ArrayList<>();
static HashMap<String, String> phoneBook = new HashMap<>();
static ArrayDeque<String> undoStack = new ArrayDeque<>();
static LinkedList<String> recentSearches = new LinkedList<>();
static final int MAX_RECENT = 5;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Contact Book ===");
while (running) {
System.out.println("\n1. Add Contact");
System.out.println("2. Search Contact");
System.out.println("3. Delete Contact");
System.out.println("4. Undo Delete");
System.out.println("5. Show All Contacts");
System.out.println("7. Exit");
System.out.print("Choose an option: ");
String choice = scanner.nextLine();
switch (choice) {
case "1":
addContact(scanner);
break;
case "2":
searchContact(scanner);
break;
case "3":
deleteContact(scanner);
break;
case "4":
undoDelete();
break;
case "5":
showAllContacts();
break;
case "7":
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid option. Try again.");
}
}
scanner.close();
}
static void addContact(Scanner scanner) {
System.out.print("Enter name: ");
String name = scanner.nextLine();
System.out.print("Enter phone number: ");
String phone = scanner.nextLine();
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Contact added: " + name + " -> " + phone);
}
static void searchContact(Scanner scanner) {
System.out.print("Enter name to search: ");
String name = scanner.nextLine();
recentSearches.addLast(name);
if (recentSearches.size() > MAX_RECENT) {
recentSearches.removeFirst();
}
if (phoneBook.containsKey(name)) {
System.out.println("Found: " + name + " -> " + phoneBook.get(name));
} else {
System.out.println("Contact not found: " + name);
}
}
static void deleteContact(Scanner scanner) {
System.out.print("Enter name to delete: ");
String name = scanner.nextLine();
if (phoneBook.containsKey(name)) {
String phone = phoneBook.get(name);
undoStack.push(name + ":" + phone);
contactNames.remove(name);
phoneBook.remove(name);
System.out.println("Deleted: " + name);
System.out.println("(You can undo this action)");
} else {
System.out.println("Contact not found: " + name);
}
}
static void undoDelete() {
if (undoStack.isEmpty()) {
System.out.println("Nothing to undo.");
return;
}
String entry = undoStack.pop();
String[] parts = entry.split(":", 2);
String name = parts[0];
String phone = parts[1];
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Restored: " + name + " -> " + phone);
}
static void showAllContacts() {
if (contactNames.isEmpty()) {
System.out.println("No contacts yet.");
return;
}
System.out.println("\n--- All Contacts ---");
for (int i = 0; i < contactNames.size(); i++) {
String name = contactNames.get(i);
System.out.println((i + 1) + ". " + name + " -> " + phoneBook.get(name));
}
}
}
Your Contact Book now has a working safety net. Delete and restore contacts with confidence. Next up, you'll add a queue that tracks your recent searches and automatically drops the oldest ones.
Add Queue-Based Recent Searches with LinkedList
In the last step, you built undo functionality using a stack (LIFO: last in, first out). Now you'll work with the opposite pattern: a queue.
A queue follows FIFO (First In, First Out) order. The first item added is the first item removed. This is perfect for tracking recent search history, where the oldest search should drop off first when the list gets full.
In this step, get ready to:
- Understand how searchContact already uses a LinkedList as a FIFO queue.
- Build the showRecentSearches method to display queue contents.
- Wire up menu option 6 and test FIFO behavior by searching for 6+ contacts.
Understand the queue logic in searchContact
Your searchContact method already records every search using the recentSearches LinkedList. Take a look at the queue logic that already exists in your file:
recentSearches.addLast(name);
if (recentSearches.size() > MAX_RECENT) {
recentSearches.removeFirst();
}
What does this code do?
- addLast(name) appends the search term to the back (tail) of the queue. New searches always join at the end.
- If the queue grows beyond MAX_RECENT (5), removeFirst() drops the oldest entry from the front (head).
- This is FIFO in action: the first search added is the first one removed when the queue overflows.
Why LinkedList for a queue?
The Java Collections Framework states that when accessed through the Queue interface, LinkedList acts as a FIFO queue. Adding to the tail and removing from the head are both O(1) operations on a linked list, making it efficient for this use case.
Compare this to an ArrayList, where removing the first element requires shifting every other element forward. For a queue pattern, LinkedList is the better choice.
Build the showRecentSearches method
The queue records searches, but there is no way to display them yet. You need a method that prints the queue contents from oldest to newest.
- In ContactBook.java, scroll to the bottom of the file (after the showAllContacts method's closing brace).
- Add the showRecentSearches method by pasting this code:
static void showRecentSearches() {
// Check if there are any searches recorded
if (recentSearches.isEmpty()) {
System.out.println("No recent searches.");
return;
}
// Iterate through the queue from front (oldest) to back (newest)
System.out.println("\n--- Recent Searches (oldest to newest) ---");
int count = 1;
for (String search : recentSearches) {
System.out.println(count + ". " + search);
count++;
}
}
What does this code do?
- recentSearches.isEmpty() checks whether anyone has searched yet. If not, it prints a message and returns early.
- The for-each loop iterates from the front (oldest) to the back (newest) of the LinkedList. This is the natural iteration order for a queue.
- A numbered counter makes the output easy to read.
- Save ContactBook.java.
Seeing a compilation error?
- Make sure the method is placed inside the ContactBook class (before the final closing }) but outside any other method.
- Check that the method starts with static void showRecentSearches() and has matching braces.
Help me debug my showRecentSearches compilation error
Wire up menu option 6 and test
The method exists, but the menu doesn't offer it yet. You need to add the display line and connect the switch case.
- In ContactBook.java, find this line in the menu display:
System.out.println("5. Show All Contacts");
System.out.println("7. Exit");
- Add a new line between them so the three lines look like this:
System.out.println("5. Show All Contacts");
System.out.println("6. Show Recent Searches");
System.out.println("7. Exit");
- Now find this section in the switch statement:
case "5":
showAllContacts();
break;
case "7":
running = false;
System.out.println("Goodbye!");
break;
- Add case "6" between them so it looks like this:
case "5":
showAllContacts();
break;
case "6":
showRecentSearches();
break;
case "7":
running = false;
System.out.println("Goodbye!");
break;
What does this code do?
- The new println line adds option 6 to the visible menu so the user knows it exists.
- The new case "6" routes the user's input to the showRecentSearches() method you just created.
- Save ContactBook.java.
- Compile and run your program by running these commands in the terminal:
javac ContactBook.java
java ContactBook
- Add at least 6 contacts using option 1 (use any names and phone numbers).
- Search for each of your 6 contacts using option 2.
- Select option 6 to view recent searches.
You should see only the 5 most recent searches listed from oldest to newest. Your very first search is gone because the queue dropped it when the 6th search arrived.
Still seeing all 6 searches?
- Double-check that MAX_RECENT is set to 5 at the top of your class.
- Verify the if (recentSearches.size() > MAX_RECENT) check exists inside your searchContact method.
- Make sure you recompiled with javac ContactBook.java before running again.
Help me debug my recent searches queue
✔️ Awesome, I've got everything!
Great work. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete ContactBook.java file at this point:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Scanner;
public class ContactBook {
static ArrayList<String> contactNames = new ArrayList<>();
static HashMap<String, String> phoneBook = new HashMap<>();
static ArrayDeque<String> undoStack = new ArrayDeque<>();
static LinkedList<String> recentSearches = new LinkedList<>();
static final int MAX_RECENT = 5;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Contact Book ===");
while (running) {
System.out.println("\n1. Add Contact");
System.out.println("2. Search Contact");
System.out.println("3. Delete Contact");
System.out.println("4. Undo Delete");
System.out.println("5. Show All Contacts");
System.out.println("6. Show Recent Searches");
System.out.println("7. Exit");
System.out.print("Choose an option: ");
String choice = scanner.nextLine();
switch (choice) {
case "1":
addContact(scanner);
break;
case "2":
searchContact(scanner);
break;
case "3":
deleteContact(scanner);
break;
case "4":
undoDelete();
break;
case "5":
showAllContacts();
break;
case "6":
showRecentSearches();
break;
case "7":
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid option. Try again.");
}
}
scanner.close();
}
static void addContact(Scanner scanner) {
System.out.print("Enter name: ");
String name = scanner.nextLine();
System.out.print("Enter phone number: ");
String phone = scanner.nextLine();
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Contact added: " + name + " -> " + phone);
}
static void searchContact(Scanner scanner) {
System.out.print("Enter name to search: ");
String name = scanner.nextLine();
recentSearches.addLast(name);
if (recentSearches.size() > MAX_RECENT) {
recentSearches.removeFirst();
}
if (phoneBook.containsKey(name)) {
System.out.println("Found: " + name + " -> " + phoneBook.get(name));
} else {
System.out.println("Contact not found: " + name);
}
}
static void deleteContact(Scanner scanner) {
System.out.print("Enter name to delete: ");
String name = scanner.nextLine();
if (phoneBook.containsKey(name)) {
String phone = phoneBook.get(name);
undoStack.push(name + ":" + phone);
contactNames.remove(name);
phoneBook.remove(name);
System.out.println("Deleted: " + name);
System.out.println("(You can undo this action)");
} else {
System.out.println("Contact not found: " + name);
}
}
static void undoDelete() {
if (undoStack.isEmpty()) {
System.out.println("Nothing to undo.");
return;
}
String entry = undoStack.pop();
String[] parts = entry.split(":", 2);
String name = parts[0];
String phone = parts[1];
contactNames.add(name);
phoneBook.put(name, phone);
System.out.println("Restored: " + name + " -> " + phone);
}
static void showAllContacts() {
if (contactNames.isEmpty()) {
System.out.println("No contacts yet.");
return;
}
System.out.println("\n--- All Contacts ---");
for (int i = 0; i < contactNames.size(); i++) {
String name = contactNames.get(i);
System.out.println((i + 1) + ". " + name + " -> " + phoneBook.get(name));
}
}
static void showRecentSearches() {
if (recentSearches.isEmpty()) {
System.out.println("No recent searches.");
return;
}
System.out.println("\n--- Recent Searches (oldest to newest) ---");
int count = 1;
for (String search : recentSearches) {
System.out.println(count + ". " + search);
count++;
}
}
}
Your Contact Book is now fully functional with all four data structures working together. Each one handles a different job: ArrayList for ordered storage, HashMap for fast lookups, ArrayDeque for LIFO undo, and LinkedList for FIFO search history.
Secret mission
Add a Favorites Feature with LinkedHashSet
Your Contact Book already handles adding, searching, deleting, and undoing. But what if you want to mark certain contacts as favorites? In this secret mission, you'll use a LinkedHashSet to build a favorites list that prevents duplicates and remembers the order you added them.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your files for future reference or delete them entirely.
Resources you used:
- ContactBook project folder on your Desktop (contains ContactBook.java and compiled .class files).
- JDK 25 installed on your machine.
Keep everything running
No action needed. Choose this if you plan to keep building with Java or want to revisit your Contact Book code later.
- Your ContactBook folder stays on your Desktop.
- JDK 25 remains installed and available for future Java projects.
Pause - I'll come back to this later
There are no running processes or services to pause. Your files sit on disk and consume minimal space.
- No action required. Your project folder and JDK installation take up disk space but use no CPU or memory while idle.
Delete - I don't want to use this again
Remove all project files and optionally uninstall JDK 25 to free up disk space.
Delete the project folder:
macOS
- Delete your project folder by running this command in Terminal:
rm -rf ~/Desktop/ContactBook
Windows
- Delete your project folder by running this command in PowerShell:
Remove-Item -Recurse -Force ~\Desktop\ContactBook
Uninstall JDK 25:
macOS
- Remove the JDK directory by running:
sudo rm -rf /Library/Java/JavaVirtualMachines/jdk-25.jdk
Windows
- Open Settings.
- Select Apps, then Installed apps.
- Search for Java, click the three-dot menu next to Java(TM) SE Development Kit 25, and select Uninstall.
Nice Work!
Nice Work!
Nice work! You just built a fully functional command-line Contact Book application in Java that puts four core data structures to work.
You now know the most important skill in data structures: choosing the right one for the job.
You've learned how to:
- Use an ArrayList for ordered storage with fast index-based access and a HashMap for O(1) key-based lookup to build a contact management system.
- Implement stack-based undo functionality with an ArrayDeque, using LIFO behavior to restore the most recently deleted contact first.
- Track recent search history with a LinkedList acting as a FIFO queue, automatically dropping the oldest entry when the queue is full.
- Secret Mission: Added a Favorites feature using a LinkedHashSet that prevents duplicate entries while preserving insertion order.
Ready to quiz yourself?