What is Java?
Before writing a single line, let's understand what Java actually is, why companies hire Java developers, and what you'll be able to build by the end of this course.
Java is one of the most widely used programming languages in the world. It powers Android apps, large-scale enterprise systems at banks and insurance companies, backend APIs, and much more. Learning Java opens doors to a huge number of jobs — it consistently ranks in the top 3 most in-demand languages for developers.
Why Java for your first language?
Huge job market
Java developers are among the most hired in tech. Banks, healthcare, government, startups — all use Java.
Teaches you to think
Java forces you to be organized. The habits you build here will make you a better developer in any language.
Write once, run anywhere
Java runs on any device with a JVM installed — Windows, Mac, Linux, Android. No rewriting for each platform.
Massive ecosystem
Thousands of libraries and frameworks. Spring, Maven, Gradle, JUnit — the tools are all there waiting for you.
How Java runs: the JVM
Most languages compile directly to machine code your CPU understands. Java does something different: it compiles to bytecode, a middle format that any computer can understand — as long as it has the Java Virtual Machine (JVM) installed.
This is why Java is "write once, run anywhere." You write the code once, compile it once, and that compiled file runs identically everywhere.
What you'll build by the end
This course takes you from zero to a working task management console application — a real program that stores tasks, lets you add and complete them, and reads/writes to a file. It's exactly the kind of project you'd put on a resume for a junior developer role.
Along the way you'll learn every concept a junior developer needs: variables, loops, methods, object-oriented design, error handling, and file I/O.
You can follow all early lessons using an online Java editor like replit.com or jdoodle.com. When you're ready to work locally, we'll walk through installing JDK + IntelliJ IDEA.
Your First Program
Every Java developer's journey starts here — the classic "Hello, World!" But we're going to understand every word of it, not just copy-paste.
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }
Let's break down every single piece:
public class HelloWorld
In Java, all code lives inside a class. Think of a class as a container. The name of the class (HelloWorld) must match the file name exactly — HelloWorld.java. public means other parts of the program can see it.
public static void main(String[] args)
This is the entry point — it's the exact method the JVM looks for and calls first when your program runs. Every Java program must have this. For now, memorize it as a formula; the pieces will make sense as you progress.
System.out.println("Hello, World!");
System.out represents the console (the black terminal window). println prints text and moves to a new line. The text you want to print goes inside the parentheses as a String (text in double quotes). Every statement ends with a semicolon ;.
Curly braces { }
Curly braces group code together. The outer pair belongs to the class, the inner pair belongs to the main method. Everything inside a pair of braces is considered to be "inside" that block.
System is not the same as system. String is not the same as string. This is one of the most common beginner errors. If your code isn't working, check your capitalisation first.
public class Greetings { public static void main(String[] args) { System.out.println("Hello, World!"); // new line after System.out.print("Java is "); // no new line — stays on same line System.out.println("awesome!"); // Output: // Hello, World! // Java is awesome! } }
Lines starting with // are comments — they're notes for humans and are completely ignored by Java. Use them to explain what your code does. Good comments make you a better teammate and future-you will thank present-you.
Write a program that introduces yourself
Create a class called Intro. In the main method, print three lines:
- Your name
- Your city or country
- One hobby or interest
Use println for each line. Check: does your file name match your class name?
Variables & Data Types
Variables are named storage boxes for your program's data. Java requires you to say exactly what kind of data each box will hold — this is called static typing and it's one of Java's biggest strengths.
Unlike some languages where you can just write x = 5, Java requires a type declaration: you must tell the compiler what kind of data the variable will hold before you use it. This catches mistakes before your program even runs.
public class DataTypes { public static void main(String[] args) { // ─── Integer (whole numbers) ────────────────── int age = 25; // -2 billion to +2 billion long population = 8_000_000_000L; // big numbers, note the L // ─── Decimal (floating point) ───────────────── double price = 19.99; // most common for decimals float rating = 4.5f; // less precise, note the f // ─── Text ───────────────────────────────────── char grade = 'A'; // single character, single quotes String name = "Alex"; // text, double quotes. Note: capital S! // ─── True/False ─────────────────────────────── boolean isLoggedIn = true; // only true or false boolean hasDiscount = false; // Printing variables System.out.println("Name: " + name); // + joins text with variables System.out.println("Age: " + age); System.out.println("Price: $" + price); } }
| Type | Stores | Example value | Use when |
|---|---|---|---|
| int | Whole numbers | 42, -7, 1000 | Age, count, score |
| long | Very large whole numbers | 8000000000L | Timestamps, large IDs |
| double | Decimal numbers | 3.14, 19.99 | Prices, measurements |
| boolean | True or false | true, false | Flags, conditions |
| char | One character | 'A', '7', '!' | Single letters/symbols |
| String | Text (any length) | "Hello" | Names, messages, IDs |
Declaring vs. Assigning
You can separate the declaration (creating the box) from the assignment (putting something in it):
// Declare first, assign later int score; score = 100; // Declare and assign in one line (most common) int lives = 3; // var — Java 10+: compiler figures out the type for you var message = "Hello"; // Java infers: String var count = 42; // Java infers: int // final = a constant. Value can NEVER change after assignment final double PI = 3.14159; // PI = 3.0; // ERROR: cannot assign a value to final variable // Naming convention: camelCase for variables int numberOfStudents = 30; // camelCase: first word lowercase, rest capitalized final int MAX_SIZE = 100; // constants: ALL_CAPS with underscores
String starts with a capital letter — it's not a primitive, it's a class (more on that in the OOP lessons). The 8 primitives all start lowercase: int, double, boolean, char, long, float, short, byte.
Student Profile
Create a class StudentProfile with a main method. Declare and assign variables for:
- A student's full name (String)
- Their age (int)
- Their GPA (double)
- Whether they're enrolled (boolean)
- Their grade (char, like 'B')
Print each piece of information on its own line, like: Name: Jordan Lee
Operators & Expressions
Operators let you do math, compare values, and combine conditions. These are the verbs of your programs — they make things happen.
public class Operators { public static void main(String[] args) { // ─── Arithmetic ─────────────────────────────── int a = 10, b = 3; System.out.println(a + b); // 13 — addition System.out.println(a - b); // 7 — subtraction System.out.println(a * b); // 30 — multiplication System.out.println(a / b); // 3 — integer division (truncates!) System.out.println(a % b); // 1 — modulo (remainder) // Integer division gotcha: double result = (double) a / b; // 3.333... (cast to double first!) // ─── Shorthand assignment ───────────────────── int score = 50; score += 10; // same as: score = score + 10 → 60 score -= 5; // → 55 score *= 2; // → 110 score++; // increment by 1 → 111 score--; // decrement by 1 → 110 // ─── Comparison (always return boolean) ────── System.out.println(a == b); // false — equal to System.out.println(a != b); // true — not equal System.out.println(a > b); // true — greater than System.out.println(a <= b); // false — less than or equal // ─── Logical ────────────────────────────────── boolean isAdult = true; boolean hasTicket = false; System.out.println(isAdult && hasTicket); // false — AND: both must be true System.out.println(isAdult || hasTicket); // true — OR: at least one true System.out.println(!isAdult); // false — NOT: flips the value // ─── String concatenation ───────────────────── String first = "Java"; String last = "Developer"; System.out.println(first + " " + last); // "Java Developer" // printf for formatted output (like a template) double price = 9.5; System.out.printf("Price: $%.2f%n", price); // Price: $9.50 } }
10 / 3 in Java gives 3, not 3.333. When both operands are int, Java throws away the decimal. To get a decimal result, cast one number to double first: (double) 10 / 3 → 3.333. This trips up almost every beginner.
Shopping Cart Calculator
Write a program that calculates the total cost of a shopping cart:
- Three items with prices of your choice (use
double) - Calculate the subtotal (sum of all items)
- Apply a 10% discount (multiply by 0.9)
- Add 8% sales tax to the discounted price (multiply by 1.08)
- Print the subtotal, discount amount, tax amount, and final total using
printfwith 2 decimal places
17 % 5 evaluate to?Control Flow: if / else
Programs need to make decisions. The if/else statement is how your code chooses what to do based on conditions. It's the foundation of all program logic.
public class Decisions { public static void main(String[] args) { int age = 20; // Simple if/else if (age >= 18) { System.out.println("You can vote!"); } else { System.out.println("Too young to vote."); } // Multiple conditions with else if int score = 85; String grade; if (score >= 90) { grade = "A"; } else if (score >= 80) { grade = "B"; } else if (score >= 70) { grade = "C"; } else if (score >= 60) { grade = "D"; } else { grade = "F"; } System.out.println("Grade: " + grade); // Grade: B // Combining conditions boolean hasID = true; boolean isVIPMember = false; int memberScore = 750; if (hasID && (isVIPMember || memberScore > 700)) { System.out.println("Welcome to the lounge!"); } // Switch statement — clean alternative to many else-ifs String day = "MONDAY"; switch (day) { case "MONDAY": case "TUESDAY": case "WEDNESDAY": case "THURSDAY": case "FRIDAY": System.out.println("Weekday — work day!"); break; case "SATURDAY": case "SUNDAY": System.out.println("Weekend!"); break; default: System.out.println("Unknown day"); } // Ternary operator — shorthand if/else on one line int temp = 28; String weather = (temp > 25) ? "Hot" : "Cool"; System.out.println(weather); // Hot } }
Build a Movie Ticket Pricing System
Write a program that calculates a movie ticket price based on age and day of the week:
- Under 12: $8.00
- 12–17: $10.00
- 18–64: $14.00
- 65+: $9.00
- If it's Tuesday ("TUESDAY"), everyone gets $3 off
Create variables for age and day, calculate the price, and print the result. Test with at least 3 different combinations.
age >= 18 && hasLicense evaluates to true when…Loops
Loops let your program repeat actions without copy-pasting code. Instead of printing 100 lines by hand, one loop does it in 3 lines. They're indispensable.
public class Loops { public static void main(String[] args) { // ─── for loop: when you know how many times ──── for (int i = 1; i <= 5; i++) { System.out.println("Count: " + i); } // Prints: Count: 1, Count: 2 ... Count: 5 // ↑init ↑condition ↑update (runs after each iteration) // Counting down for (int i = 10; i > 0; i--) { System.out.print(i + " "); } System.out.println(); // blank println = new line // ─── while loop: when you don't know how many ─ int coins = 100; while (coins > 0) { System.out.println("Coins remaining: " + coins); coins -= 25; } System.out.println("Out of coins!"); // ─── do-while: always runs at least once ────── int attempts = 0; do { System.out.println("Attempt #" + (attempts + 1)); attempts++; } while (attempts < 3); // ─── break: exit loop early ─────────────────── for (int i = 1; i <= 100; i++) { if (i % 7 == 0) { System.out.println("First multiple of 7: " + i); break; // exit the loop immediately } } // ─── continue: skip this iteration ─────────── for (int i = 1; i <= 10; i++) { if (i % 2 == 0) continue; // skip even numbers System.out.print(i + " "); // prints: 1 3 5 7 9 } } }
If your loop condition never becomes false, the program hangs forever. Always make sure your loop variable is moving toward the condition being false. If your program freezes, that's usually why. Press Ctrl+C to stop it.
// Nested loops: a loop inside a loop for (int row = 1; row <= 5; row++) { for (int col = 1; col <= 5; col++) { System.out.printf("%4d", row * col); } System.out.println(); } // Output: // 1 2 3 4 5 // 2 4 6 8 10 // 3 6 9 12 15 // ...
FizzBuzz — the classic interview question
Write a loop from 1 to 100. For each number:
- If divisible by both 3 and 5: print
FizzBuzz - If divisible by 3 only: print
Fizz - If divisible by 5 only: print
Buzz - Otherwise: print the number
This question appears in real Java developer interviews. If you can write this cleanly, you're on your way.
break and continue inside a loop?Methods
Methods are named blocks of reusable code. Instead of writing the same logic over and over, you write it once in a method and call it by name. They're how real programs stay organized.
public class Methods { // ── Method anatomy ────────────────────────────────────── // access return-type name (parameters) // ↓ ↓ ↓ ↓ public static void greet (String name) { System.out.println("Hello, " + name + "!"); } // void = returns nothing // Method that returns a value public static int add(int x, int y) { return x + y; // return sends a value back to the caller } // Method with multiple parameters and logic public static double calculateBMI(double weightKg, double heightM) { return weightKg / (heightM * heightM); } // Method that returns a boolean public static boolean isEven(int number) { return number % 2 == 0; } // Method that calls other methods public static String getBMICategory(double weightKg, double heightM) { double bmi = calculateBMI(weightKg, heightM); // calls the method above if (bmi < 18.5) return "Underweight"; else if (bmi < 25.0) return "Normal"; else if (bmi < 30.0) return "Overweight"; else return "Obese"; } public static void main(String[] args) { greet("Alex"); // Hello, Alex! greet("World"); // Hello, World! int sum = add(3, 7); System.out.println("Sum: " + sum); // Sum: 10 System.out.println(isEven(4)); // true System.out.println(isEven(7)); // false String category = getBMICategory(70, 1.75); System.out.println("BMI: " + category); // BMI: Normal } }
Each method should do exactly one thing. A method called calculateAndPrintTax is doing two things — calculating AND printing. Better: one method calculates and returns the tax, a different one prints it. This makes your code easier to test and reuse.
Method overloading
Java lets you have multiple methods with the same name, as long as their parameters differ. This is called overloading.
public static double area(double side) { return side * side; // square } public static double area(double width, double height) { return width * height; // rectangle } public static double area(double radius, boolean isCircle) { return Math.PI * radius * radius; // circle } // Java picks the right version based on arguments you provide area(5.0); // → 25.0 (square) area(4.0, 6.0); // → 24.0 (rectangle) area(3.0, true); // → 28.27 (circle)
Build a Calculator Utility
Create a class Calculator with these methods:
add(double a, double b)→ returns sumsubtract(double a, double b)→ returns differencemultiply(double a, double b)→ returns productdivide(double a, double b)→ returns quotient, but returns 0 and prints an error if b is 0power(double base, int exponent)→ returns base raised to exponent (use a loop, not Math.pow)
In main, call each method and print the results.
public static int square(int n). What must happen inside the method body?Arrays
An array is a list of values stored under one variable name. Instead of creating score1, score2, score3... you create one array that holds all of them.
public class ArraysDemo { public static void main(String[] args) { // ─── Creating arrays ────────────────────────── int[] scores = {85, 92, 78, 95, 88}; // array literal String[] names = new String[3]; // empty array of size 3 names[0] = "Alice"; // assign by index (starts at 0!) names[1] = "Bob"; names[2] = "Carol"; // ─── Accessing elements ─────────────────────── System.out.println(scores[0]); // 85 — first element System.out.println(scores[4]); // 88 — last element System.out.println(scores.length); // 5 — size of array // ─── Loop through array ─────────────────────── int total = 0; for (int i = 0; i < scores.length; i++) { total += scores[i]; } double average = (double) total / scores.length; System.out.printf("Average: %.1f%n", average); // 87.6 // ─── for-each loop: cleaner when you just need values for (String name : names) { System.out.println("Hello, " + name); } // ─── Arrays utility methods ─────────────────── import java.util.Arrays; // add this at the top of your file Arrays.sort(scores); System.out.println(Arrays.toString(scores)); // [78, 85, 88, 92, 95] // ─── 2D array: rows and columns ─────────────── int[][] grid = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; System.out.println(grid[1][2]); // 6 — row 1, column 2 } }
Arrays in Java start at index 0, not 1. A 5-element array has valid indices 0–4. Trying to access index 5 crashes with ArrayIndexOutOfBoundsException. This is the most common array error. Always use array.length - 1 for the last element, or use a for-each loop.
Grade Statistics
Create an array of 10 test scores (make them up). Write separate methods that:
- Return the highest score
- Return the lowest score
- Return the average
- Count how many scores are above 80
Call all methods from main and print a formatted report. Bonus: sort the array and print it in order.
int[] nums = {10, 20, 30, 40, 50}. What is nums[3]?ArrayList & Collections
Arrays are fixed size — you can't add or remove elements. In real applications, data changes all the time. ArrayList is the go-to resizable list that professional Java developers use every day.
import java.util.ArrayList; import java.util.HashMap; import java.util.Collections; public class ListsDemo { public static void main(String[] args) { // ─── ArrayList: resizable list ──────────────── ArrayList<String> tasks = new ArrayList<>(); tasks.add("Buy groceries"); // add to end tasks.add("Walk the dog"); tasks.add("Do laundry"); tasks.add(0, "Wake up"); // insert at index 0 System.out.println(tasks.size()); // 4 System.out.println(tasks.get(1)); // "Buy groceries" System.out.println(tasks.contains("Walk the dog")); // true tasks.remove("Do laundry"); // remove by value tasks.remove(0); // remove by index tasks.set(0, "Go shopping"); // update existing item // For-each loop works great with ArrayList for (String task : tasks) { System.out.println("- " + task); } Collections.sort(tasks); // alphabetical sort Collections.reverse(tasks); // reverse the list // ─── HashMap: key-value pairs ───────────────── // Like a dictionary: look up a value by its key HashMap<String, Integer> ages = new HashMap<>(); ages.put("Alice", 30); ages.put("Bob", 25); ages.put("Carol", 35); System.out.println(ages.get("Bob")); // 25 System.out.println(ages.containsKey("Alice")); // true // Loop through a HashMap for (String name : ages.keySet()) { System.out.println(name + " is " + ages.get(name)); } } }
| ArrayList | Regular Array |
|---|---|
| Resizable — add/remove freely | Fixed size once created |
| get(i), add(), remove() | array[i] direct access |
| Works with Collections utilities | Works with Arrays utilities |
| Slightly slower (boxing overhead) | Faster for fixed-size data |
| Can't store primitives directly | Can store int, double, etc. |
Mini Contact Book
Build a contact book using a HashMap<String, String> where the key is a name and the value is a phone number. Write methods to:
- Add a contact
- Look up a contact by name (handle the case where they don't exist)
- Delete a contact
- Print all contacts in alphabetical order (hint: convert keys to an ArrayList and sort it)
ArrayList<String> list with 3 items. After list.add("new item"), what does list.size() return?Strings in Depth
Strings are everywhere — usernames, messages, file paths, API responses. Java's String class has dozens of built-in methods. Knowing them saves hours of writing code from scratch.
public class StringMethods { public static void main(String[] args) { String s = " Hello, Java World! "; // ─── Basic info ─────────────────────────────── System.out.println(s.length()); // 22 (including spaces) System.out.println(s.isEmpty()); // false System.out.println(s.isBlank()); // false (Java 11+) // ─── Searching ──────────────────────────────── System.out.println(s.contains("Java")); // true System.out.println(s.indexOf("Java")); // 9 (position) System.out.println(s.startsWith(" Hello")); // true System.out.println(s.endsWith("! ")); // true // ─── Transformation ─────────────────────────── System.out.println(s.trim()); // removes leading/trailing spaces System.out.println(s.toLowerCase()); // " hello, java world! " System.out.println(s.toUpperCase()); // " HELLO, JAVA WORLD! " System.out.println(s.replace("Java", "Python")); // replaces text // ─── Extracting parts ───────────────────────── String trimmed = s.trim(); System.out.println(trimmed.substring(7)); // "Java World!" System.out.println(trimmed.substring(7, 11)); // "Java" (7 to 10) System.out.println(trimmed.charAt(0)); // 'H' // ─── Splitting ──────────────────────────────── String csv = "Alice,Bob,Carol,Dave"; String[] parts = csv.split(","); for (String name : parts) { System.out.println(name); // Alice, Bob, Carol, Dave } // ─── CRITICAL: comparing Strings ────────────── String a = "hello"; String b = "hello"; System.out.println(a == b); // true (but don't rely on this!) System.out.println(a.equals(b)); // true — ALWAYS use .equals() System.out.println(a.equalsIgnoreCase("HELLO")); // true // ─── StringBuilder: for building strings in loops StringBuilder sb = new StringBuilder(); for (int i = 1; i <= 5; i++) { sb.append("Item ").append(i).append("\n"); } System.out.print(sb.toString()); } }
== checks if two variables point to the same memory location, not if they have the same content. Two different String objects with the same text can fail a == check. Always use .equals() to compare String content. This is one of the most common Java bugs in beginner code.
Username Validator
Write a method validateUsername(String username) that returns a message based on these rules:
- Must be 3–20 characters long
- Must not contain spaces
- Must start with a letter (not a number or symbol)
- If all rules pass: return "Username is valid!"
- If a rule fails: return a specific message saying which rule failed
Test it with at least 5 different usernames — some valid, some not.
Classes & Objects
This is the big one. Object-Oriented Programming (OOP) is the foundation of professional Java. A class is a blueprint; an object is a thing built from that blueprint. Everything in Java is built this way.
Think of a class like an architectural drawing for a house. The drawing defines what every house built from it will have: rooms, doors, windows. An object is a specific house built from that drawing — your house, my house. Each is its own thing with its own data, but both were built the same way.
Defining a class
public class BankAccount { // ─── Fields (instance variables) ────────────── // private = only this class can access them directly private String owner; private double balance; private String accountId; // ─── Constructor: called when you do "new BankAccount()" ── public BankAccount(String owner, double initialBalance) { this.owner = owner; // "this" refers to THIS object this.balance = initialBalance; this.accountId = "ACC-" + (int)(Math.random() * 9000 + 1000); } // ─── Methods (behaviours) ───────────────────── public void deposit(double amount) { if (amount <= 0) { System.out.println("Invalid deposit amount."); return; } balance += amount; System.out.printf("Deposited $%.2f. New balance: $%.2f%n", amount, balance); } public void withdraw(double amount) { if (amount > balance) { System.out.println("Insufficient funds."); return; } balance -= amount; System.out.printf("Withdrew $%.2f. New balance: $%.2f%n", amount, balance); } // ─── Getters: controlled read access ────────── public String getOwner() { return owner; } public double getBalance() { return balance; } public String getAccountId(){ return accountId; } // ─── toString: how to print the object ──────── @Override public String toString() { return String.format("[%s] %s — $%.2f", accountId, owner, balance); } }
public class Main { public static void main(String[] args) { // Creating objects with "new" — calls the constructor BankAccount alice = new BankAccount("Alice", 1000.00); BankAccount bob = new BankAccount("Bob", 500.00); // Calling methods on each object alice.deposit(250.00); alice.withdraw(100.00); bob.withdraw(700.00); // Insufficient funds // Using getters System.out.println(alice.getOwner() + " has $" + alice.getBalance()); // toString is called automatically when printing an object System.out.println(alice); // [ACC-4821] Alice — $1150.00 System.out.println(bob); // [ACC-7392] Bob — $500.00 // Each object has its OWN data — they don't share balance ArrayList<BankAccount> accounts = new ArrayList<>(); accounts.add(alice); accounts.add(bob); for (BankAccount acc : accounts) { System.out.println(acc); } } }
Making fields private means nothing outside the class can directly change them. Someone can't write alice.balance = -99999 and corrupt your data. They must go through deposit() and withdraw() — which have your validation logic. This protection is called encapsulation and it's one of the most important ideas in OOP.
Build a Student class
Create a Student class with:
- Private fields:
name(String),studentId(int),grades(ArrayList of doubles) - Constructor that sets name and generates a random 5-digit student ID
- Method
addGrade(double grade)— validates grade is 0–100 - Method
getAverage()— returns average of all grades (return 0 if no grades yet) - Method
getLetterGrade()— returns "A", "B", "C", "D", or "F" based on average - Override
toString()to print all info nicely
In main, create 3 students, add grades to each, and print their reports.
Inheritance
Inheritance lets one class build on top of another. If you have common features shared by multiple classes, put them in a parent class. Child classes inherit everything and add their own unique behavior — no copy-pasting.
Imagine you're building a zoo app. Animals all have a name, an age, and make a sound. But a Dog fetches, a Bird flies, and a Fish swims. Inheritance lets you put the shared stuff in Animal and the unique stuff in each subclass.
// ─── Parent class (Superclass) ───────────────── public class Animal { protected String name; // protected = accessible in subclasses protected int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println(name + " is eating."); } public void sleep() { System.out.println(name + " is sleeping."); } // This method will be OVERRIDDEN by subclasses public void makeSound() { System.out.println(name + " makes a sound."); } @Override public String toString() { return name + " (age " + age + ")"; } } // ─── Child class (Subclass) ──────────────────── public class Dog extends Animal { // "extends" = inherit from Animal private String breed; public Dog(String name, int age, String breed) { super(name, age); // call Animal's constructor first this.breed = breed; } @Override // tells Java we're replacing the parent's version public void makeSound() { System.out.println(name + " says: Woof!"); } public void fetch() { // Dog-specific method System.out.println(name + " fetches the ball!"); } @Override public String toString() { return super.toString() + " — " + breed; // reuse parent's toString } } public class Cat extends Animal { private boolean isIndoor; public Cat(String name, int age, boolean isIndoor) { super(name, age); this.isIndoor = isIndoor; } @Override public void makeSound() { System.out.println(name + " says: Meow!"); } } // ─── Polymorphism: the power of inheritance ──── public class Zoo { public static void main(String[] args) { // A Dog IS-AN Animal, so this is allowed: Animal[] animals = { new Dog("Rex", 3, "Labrador"), new Cat("Whiskers", 5, true), new Dog("Buddy", 2, "Poodle") }; // Same method call, different behaviour — POLYMORPHISM for (Animal a : animals) { a.makeSound(); // calls the right version for each animal a.eat(); // inherited from Animal } // Casting back to subtype to access subclass methods if (animals[0] instanceof Dog dog) { dog.fetch(); // Rex fetches the ball! } } }
Inheritance should only be used when there's a genuine "is-a" relationship. A Dog IS AN Animal ✅. A Car is NOT an Engine ❌ — a car has an engine. When in doubt, ask: "Does this child truly IS-A parent?" If not, use composition (a class that contains another class as a field).
Shape Hierarchy
Create a parent class Shape with fields color and a method describe(). Create three subclasses:
Circlewith radius — calculates area as π×r²Rectanglewith width and height — calculates area as w×hTrianglewith base and height — calculates area as ½×b×h
Each subclass overrides a getArea() method. In main, create an array of Shape objects, loop through them, print each shape's color, type, and area. Find which shape has the largest area.
super(name, age) do inside a subclass constructor?Interfaces
An interface is a contract. It says "any class that implements me must provide these methods." Interfaces let unrelated classes share a common set of behaviours — and they're everywhere in professional Java code.
Think of an interface like a job description. "Software Developer" requires: writes code, reviews PRs, attends standups. Whether you're a junior dev at a startup or a senior at a bank, you both implement the same interface. The interface doesn't care how you do it — just that you can.
// ─── Define an interface ─────────────────────── public interface Printable { void print(); // no body — every class MUST implement this void printSummary(); // Default method: provides a body, can be overridden default void printHeader() { System.out.println("=== DOCUMENT ==="); } } public interface Saveable { void save(String filename); void load(String filename); } // ─── A class can implement MULTIPLE interfaces ─ public class Report implements Printable, Saveable { private String title; private String content; public Report(String title, String content) { this.title = title; this.content = content; } @Override public void print() { printHeader(); // uses the default method System.out.println("Title: " + title); System.out.println(content); } @Override public void printSummary() { System.out.println("Report: " + title + " (" + content.length() + " chars)"); } @Override public void save(String filename) { System.out.println("Saving '" + title + "' to " + filename); } @Override public void load(String filename) { System.out.println("Loading from " + filename); } } // ─── Real power: use the interface as a type ─── public class Printer { // This method works with ANY class that implements Printable public static void printAll(Printable[] items) { for (Printable item : items) { item.print(); System.out.println("---"); } } public static void main(String[] args) { Report r1 = new Report("Q1 Sales", "Revenue up 15%..."); Report r2 = new Report("Annual Review", "Strong performance..."); printAll(new Printable[] { r1, r2 }); r1.save("q1_report.txt"); } }
| Interface | Abstract Class | |
|---|---|---|
| Can have fields? | Only constants | Yes |
| Can have constructors? | No | Yes |
| How many can a class use? | Many (implements A, B, C) | One (extends) |
| Best for | Unrelated classes sharing behaviour | Related classes sharing code |
Payment System
Create an interface PaymentMethod with methods: pay(double amount) and getBalance(). Implement three classes:
CreditCard— has a credit limit;pay()reduces available creditDebitCard— has a bank balance;pay()reduces balance, rejects if insufficientDigitalWallet— has a wallet balance;pay()reduces balance
Write a checkout(PaymentMethod method, double total) method that works with any payment type. Demonstrate all three.
void print(Printable p))?Exception Handling
Real programs crash. Users type letters where numbers are expected, files don't exist, network connections drop. Exception handling is how you write programs that deal with errors gracefully instead of crashing.
public class Exceptions { public static void main(String[] args) { // ─── Basic try-catch ────────────────────────── try { int[] nums = {1, 2, 3}; System.out.println(nums[5]); // crashes! index out of bounds } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Error: invalid index — " + e.getMessage()); } // Program continues after the catch block // ─── Multiple catch blocks ──────────────────── try { String text = null; System.out.println(text.length()); // NullPointerException int result = 10 / 0; // ArithmeticException } catch (NullPointerException e) { System.out.println("Null reference! " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Math error: " + e.getMessage()); } catch (Exception e) { // catches anything not caught above System.out.println("Unexpected error: " + e.getMessage()); } finally { System.out.println("This ALWAYS runs — use for cleanup"); } // ─── Parsing user input safely ──────────────── String userInput = "abc"; try { int number = Integer.parseInt(userInput); System.out.println("Parsed: " + number); } catch (NumberFormatException e) { System.out.println("'" + userInput + "' is not a valid number."); } } // ─── Throwing exceptions ────────────────────── public static double divide(double a, double b) { if (b == 0) { throw new IllegalArgumentException("Cannot divide by zero!"); } return a / b; } } // ─── Custom exception ────────────────────────── public class InsufficientFundsException extends Exception { private double shortfall; public InsufficientFundsException(double shortfall) { super("Insufficient funds. Short by $" + shortfall); this.shortfall = shortfall; } public double getShortfall() { return shortfall; } } // Using the custom exception public void withdraw(double amount) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException(amount - balance); } balance -= amount; }
NullPointerException — called a method on a null reference. ArrayIndexOutOfBoundsException — accessed an invalid index. NumberFormatException — tried to parse "abc" as an integer. ClassCastException — invalid type cast. StackOverflowError — infinite recursion. Recognising these by name saves huge amounts of debugging time.
Safe Number Parser Utility
Write a class SafeParser with these static methods:
parseInt(String s)— returns the int, or -1 if invalid (never crashes)parseDouble(String s)— returns the double, or -1.0 if invalidparsePositiveInt(String s)— returns the int only if it's positive; throws a customInvalidInputExceptionif it's zero or negative or not a number
Test each method with valid input, non-numeric input, and edge cases (empty string, null, negative numbers).
finally block in a try-catch-finally do?File I/O
Programs that can't save data are toys. File I/O — reading from and writing to files — is what turns your programs into tools that remember things between runs. Every real application does this.
import java.io.*; import java.nio.file.*; import java.util.*; public class FileIO { // ─── Writing to a file ──────────────────────── public static void writeFile(String filename, String content) { // try-with-resources: automatically closes writer when done try (FileWriter writer = new FileWriter(filename)) { writer.write(content); System.out.println("Written to " + filename); } catch (IOException e) { System.out.println("Error writing file: " + e.getMessage()); } } // ─── Appending to a file ────────────────────── public static void appendFile(String filename, String line) { try (FileWriter fw = new FileWriter(filename, true); // true = append mode PrintWriter pw = new PrintWriter(fw)) { pw.println(line); } catch (IOException e) { System.out.println("Error appending: " + e.getMessage()); } } // ─── Reading line by line ───────────────────── public static List<String> readLines(String filename) { List<String> lines = new ArrayList<>(); try (BufferedReader reader = new BufferedReader(new FileReader(filename))) { String line; while ((line = reader.readLine()) != null) { lines.add(line); } } catch (FileNotFoundException e) { System.out.println("File not found: " + filename); } catch (IOException e) { System.out.println("Error reading: " + e.getMessage()); } return lines; } // ─── Modern NIO approach (Java 11+) ─────────── public static void modernReadWrite() throws IOException { Path path = Path.of("data.txt"); // Write all at once Files.writeString(path, "Line 1\nLine 2\nLine 3"); // Read all at once String content = Files.readString(path); System.out.println(content); // Read all lines as a List List<String> lines = Files.readAllLines(path); lines.forEach(System.out::println); // Check if file exists before reading if (Files.exists(path)) { System.out.println("File size: " + Files.size(path) + " bytes"); } } public static void main(String[] args) { writeFile("tasks.txt", "Buy groceries\nWalk the dog\n"); appendFile("tasks.txt", "Read a book"); List<String> tasks = readLines("tasks.txt"); System.out.println("Tasks loaded: " + tasks.size()); for (String task : tasks) { System.out.println(" □ " + task); } } }
If you open a file and don't close it, you'll eventually run out of file handles and the program crashes. The try-with-resources block (try (FileWriter fw = ...)) closes the resource automatically when the block ends — even if an exception occurs. Always use this pattern for files, database connections, and network sockets.
Persistent To-Do List
Build a program that keeps a to-do list in a file called todo.txt:
addTask(String task)— appends to the filelistTasks()— reads and prints all tasks with numberscompleteTask(int number)— removes that task from the file (read all, remove the one, rewrite)clearAll()— deletes the file or overwrites with empty content
This is a realistic mini-application. Make it handle the case where the file doesn't exist yet.
try-with-resources preferred over a regular try-finally for file handling?Capstone: Task Manager App
You've learned everything you need. Now build it all together into a real, working application — the kind you'd show in a job interview or put on GitHub.
A console-based Task Manager that persists to a file. Users can add tasks, list them, mark complete, delete, and filter by status. Every concept from this course appears in it.
public class Task { private static int nextId = 1; private int id; private String title; private String priority; // "HIGH", "MEDIUM", "LOW" private boolean completed; private String createdDate; public Task(String title, String priority) { this.id = nextId++; this.title = title; this.priority = priority.toUpperCase(); this.completed = false; this.createdDate = java.time.LocalDate.now().toString(); } // Constructor for loading from file public Task(int id, String title, String priority, boolean completed, String date) { this.id = id; this.title = title; this.priority = priority; this.completed = completed; this.createdDate = date; if (id >= nextId) nextId = id + 1; } public void complete() { completed = true; } public int getId() { return id; } public String getTitle() { return title; } public String getPriority() { return priority; } public boolean isCompleted() { return completed; } // Saves to CSV format: id,title,priority,completed,date public String toCsv() { return id + "," + title + "," + priority + "," + completed + "," + createdDate; } // Load from CSV format public static Task fromCsv(String csv) { String[] parts = csv.split(","); return new Task( Integer.parseInt(parts[0]), parts[1], parts[2], Boolean.parseBoolean(parts[3]), parts[4] ); } @Override public String toString() { String status = completed ? "[✓]" : "[ ]"; return String.format("%s #%d [%s] %s (%s)", status, id, priority, title, createdDate); } }
import java.util.*; import java.io.*; import java.nio.file.*; public class TaskManager { private static final String FILE = "tasks.csv"; private ArrayList<Task> tasks; public TaskManager() { tasks = new ArrayList<>(); loadFromFile(); // restore saved tasks on startup } public void addTask(String title, String priority) { if (title.isBlank()) { System.out.println("Task title cannot be empty."); return; } Task task = new Task(title.trim(), priority); tasks.add(task); saveToFile(); System.out.println("Added: " + task); } public void listAll() { if (tasks.isEmpty()) { System.out.println("No tasks yet. Add one!"); return; } System.out.println("\n=== YOUR TASKS ==="); for (Task t : tasks) { System.out.println(t); } } public void completeTask(int id) { for (Task t : tasks) { if (t.getId() == id) { t.complete(); saveToFile(); System.out.println("Completed: " + t.getTitle()); return; } } System.out.println("Task #" + id + " not found."); } public void deleteTask(int id) { boolean removed = tasks.removeIf(t -> t.getId() == id); if (removed) { saveToFile(); System.out.println("Deleted task #" + id); } else { System.out.println("Task #" + id + " not found."); } } public void filterByPriority(String priority) { System.out.println("\n=== " + priority.toUpperCase() + " PRIORITY ==="); boolean found = false; for (Task t : tasks) { if (t.getPriority().equalsIgnoreCase(priority)) { System.out.println(t); found = true; } } if (!found) System.out.println("No tasks with that priority."); } private void saveToFile() { try (PrintWriter pw = new PrintWriter(new FileWriter(FILE))) { for (Task t : tasks) pw.println(t.toCsv()); } catch (IOException e) { System.out.println("Could not save: " + e.getMessage()); } } private void loadFromFile() { File file = new File(FILE); if (!file.exists()) return; try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line; while ((line = br.readLine()) != null) { if (!line.isBlank()) tasks.add(Task.fromCsv(line)); } } catch (IOException e) { System.out.println("Could not load tasks: " + e.getMessage()); } } // ─── YOUR JOB: build the menu loop in main ──── public static void main(String[] args) { TaskManager manager = new TaskManager(); Scanner scanner = new Scanner(System.in); // TODO: build a while loop menu with these options: // 1. Add task → prompt title + priority // 2. List all tasks // 3. Complete task → prompt task ID // 4. Delete task → prompt task ID // 5. Filter by priority // 6. Exit // Handle invalid input with try-catch // Use scanner.nextLine() to read user input } }
Extensions to make it resume-worthy
Due dates
Add a dueDate field. Implement a listOverdue() method that shows tasks past their deadline.
Search
Add a searchTasks(String keyword) method that finds tasks whose titles contain the keyword (case-insensitive).
Statistics
Show total tasks, completed count, pending count, and completion percentage.
Sorting
Sort tasks by priority (HIGH first), by due date, or by completion status before displaying.