Project Based Learning in Java (24CSH-301 / 24ITH-301) - Lab 3
Experiment 1.3: Input Validation, ATM Withdrawal System, and University Enrolment
Develop Java programs with exception handling for user input validation,
ATM systems, and university enrolment management.
AimDesign Java programs showcasing exception handling through square root calculations, an ATM withdrawal system, and a university enrolment system with custom exceptions.
ObjectivesTo learn about the concept of inheritance, and about abstract classes and exception handling.
Mapped COCO2, CO3
Lab Brief
What You Need To Build
Easy Level
Square Root Calculator
Write a Java program to calculate the square root of a number entered by
the user. Use try-catch to handle invalid inputs (for example,
negative numbers or non-numeric values).
Read the value with Scanner.nextDouble().
throw new IllegalArgumentException for a negative number.
Catch the specific type before the general one.
Close the scanner inside finally.
Medium Level
ATM Withdrawal System
Write a Java program to simulate an ATM withdrawal system. Ask for a PIN,
allow the withdrawal only if the PIN is correct and the balance is
sufficient, throw exceptions otherwise, and always show the remaining
balance even if an exception occurs.
Write two custom checked exceptions.
Keep CORRECT_PIN = 1234 and an opening balance of 3000.0.
Handle both custom types with one multi-catch clause.
finally prints the balance on every path.
Hard Level
University Enrolment System
Create a Java program for a university enrolment system with exception
handling. Throw a CourseFullException when the maximum
enrolment limit is reached, and a PrerequisiteNotMetException
when the student has not completed the prerequisite course.
Read enrolment requests with Scanner.
Student tracks completed courses in a Set<String>.
Course.enrollStudent(Student) declares both exceptions with throws.
Use finally to show seats left after every attempt.
Input/Apparatus used:
Hardware - minimum 384 MB RAM, 100 GB hard disk. Software - JDK with any
Java IDE such as Eclipse, NetBeans, or IntelliJ IDEA, or a plain terminal
with javac and java.
From the Lab Manual
Reading Material
Abstract Classes and Methods
Data abstraction is the process of hiding certain details and showing only
essential information to the user.
Abstraction can be achieved with either abstract classes or interfaces. The
abstract keyword is a non-access modifier, used for classes and
methods:
Abstract class - a restricted class that cannot be used to
create objects. To access it, it must be inherited from another class.
Abstract method - can only be used in an abstract class, and
it does not have a body. The body is provided by the subclass.
An abstract class can have both abstract and regular methods:
abstract class Animal {
public abstract void animalSound();
public void sleep() {
System.out.println("Zzz");
}
}
From the example above, it is not possible to create an object of the Animal class:
Animal myObj = new Animal(); // will generate an error
To access the abstract class, it must be inherited from another class. Remember
from the Inheritance chapter that we use the extends keyword to
inherit from a class:
// Abstract class
abstract class Animal {
// Abstract method (does not have a body)
public abstract void animalSound();
// Regular method
public void sleep() {
System.out.println("Zzz");
}
}
// Subclass (inherit from Animal)
class Pig extends Animal {
public void animalSound() {
// The body of animalSound() is provided here
System.out.println("The pig says: wee wee");
}
}
class Main {
public static void main(String[] args) {
Pig myPig = new Pig(); // Create a Pig object
myPig.animalSound();
myPig.sleep();
}
}
Why and when to use abstract classes and methods? To achieve
security - hide certain details and only show the important details of an
object.
Java Exceptions - Try...Catch
When executing Java code, different errors can occur: coding errors made by the
programmer, errors due to wrong input, or other unforeseeable things.
When an error occurs, Java will normally stop and generate an error message.
The technical term for this is: Java will throw an exception.
The try statement allows you to define a block of code to be tested
for errors while it is being executed. The catch statement allows
you to define a block of code to be executed if an error occurs in the
try block. The try and catch keywords come
in pairs:
try {
// Block of code to try
}
catch (Exception e) {
// Block of code to handle errors
}
The following statement attempts to access an invalid array index and therefore
throws an ArrayIndexOutOfBoundsException:
public class Main {
public static void main(String[] args) {
int[] myNumbers = {1, 2, 3};
System.out.println(myNumbers[10]); // error!
}
}
Sample output:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at Main.main(Main.java:4)
A try-catch block can catch the exception and execute suitable
handling code:
public class Main {
public static void main(String[] args) {
try {
int[] myNumbers = {1, 2, 3};
System.out.println(myNumbers[10]);
} catch (Exception e) {
System.out.println("Something went wrong.");
}
}
}
Sample output:
Something went wrong.
Finally
The finally statement lets you execute code after
try...catch, regardless of the result:
public class Main {
public static void main(String[] args) {
try {
int[] myNumbers = {1, 2, 3};
System.out.println(myNumbers[10]);
} catch (Exception e) {
System.out.println("Something went wrong.");
} finally {
System.out.println("The 'try catch' is finished.");
}
}
}
Sample output:
Something went wrong.
The 'try catch' is finished.
The throw Keyword
The throw statement allows you to create a custom error. It is used
together with an exception type. There are many exception types available in
Java: ArithmeticException, FileNotFoundException,
ArrayIndexOutOfBoundsException, SecurityException, and
so on.
Throw an exception if age is below 18 (print "Access denied"). If age is 18 or
older, print "Access granted":
public class Main {
static void checkAge(int age) {
if (age < 18) {
throw new IllegalArgumentException("Access denied - You must be at least 18 years old.");
} else {
System.out.println("Access granted - You are old enough!");
}
}
public static void main(String[] args) {
checkAge(15); // Set age to 15 (which is below 18...)
}
}
Sample output:
Exception in thread "main" java.lang.IllegalArgumentException: Access denied - You must be at least 18 years old.
at Main.checkAge(Main.java:4)
at Main.main(Main.java:12)
If age was 20, you would not get an exception:
checkAge(20);
Sample output:
Access granted - You are old enough!
Why abstraction and exceptions sit in the same experiment:
both are about contracts that the compiler enforces for you. An
abstract method is a promise that every subclass will supply the
behaviour; a throws clause is a promise that every caller will deal
with the failure. In each case the compiler refuses to build the program until
the promise is kept.
Before Coding
Prerequisite Concepts
In Experiments 1.1 and 1.2 a bad input simply crashed the program. This
experiment is about the opposite: the program stays in control and decides what
happens when something goes wrong. Revise these ideas before writing any code.
The Throwable Hierarchy
Everything Java can throw descends from Throwable. Below it,
Error covers problems your program should not try to recover
from, while Exception covers the ones it should. All three
custom types in this experiment are built under Exception.
A checked exception must be declared with throws or
caught - the compiler refuses to build the program otherwise. An
unchecked one (anything under RuntimeException) carries
no such obligation. That single choice decides how a custom exception behaves.
class InvalidPinException extends Exception { }
// checked: callers are forced to handle it
class BadInput extends RuntimeException { }
// unchecked: the compiler stays silent
try, catch, finally
try holds the code that might fail, catch handles
the failure, and finally runs afterwards on every path - normal
completion, handled exception, even an uncaught one. Cleanup belongs there
and nowhere else.
Clauses are tested top to bottom, so the specific type must come before the
general one; putting catch (Exception e) first makes every later
clause unreachable and is a compile error. When two unrelated types need the
same handling, join them with |.
catch (IllegalArgumentException e) { ... } // specific first
catch (Exception e) { ... } // general last
catch (InvalidPinException | InsufficientBalanceException e) { ... }
throw vs throws
throw is a statement: it raises one exception object, right now.
throws is part of a method signature: it warns callers that this
method may fail in that way. One does the raising, the other does the
declaring.
public void enrollStudent(Set<String> done)
throws CourseFullException { // declares
if (full) {
throw new CourseFullException(msg); // raises
}
}
Writing a Custom Exception
A custom exception is an ordinary class that extends Exception.
Its constructor takes a message and hands it to super(message),
which stores it in the inherited Throwable so that
getMessage() can return it later. The class name itself is half
the documentation.
class CourseFullException extends Exception {
public CourseFullException(String message) {
super(message); // hand it to Throwable
}
}
Scanner and InputMismatchException
Typing abc at a nextDouble() prompt does not return
zero - it throws InputMismatchException, an unchecked exception
from java.util. Since it is not an
IllegalArgumentException, it falls through to the general
catch (Exception e) clause.
double n = scanner.nextDouble();
// user types: abc
// -> java.util.InputMismatchException
// caught by catch (Exception e)
getMessage() and Cleanup
e.getMessage() returns exactly the string that was passed to the
constructor, which is why a well-written message is worth the effort. And
because a thrown exception skips the rest of the try block,
scanner.close() has to sit in finally or it will be
missed on the failure path.
Problem statement: Write a Java program to calculate the square
root of a number entered by the user. Use try-catch to handle
invalid inputs (e.g., negative numbers or non-numeric values).
What Each Block Does
Block
Responsibility
try
Wraps the read, the validation, and the calculation - everything that can fail
if (number < 0) throw
Raises IllegalArgumentException with the message the user will see
catch (IllegalArgumentException e)
Specific clause - prints e.getMessage(), the exact string given to the constructor
catch (Exception e)
General safety net - catches InputMismatchException and anything else
finally
Closes the scanner on all three paths
Which Input Triggers What
Input
What happens
625
No exception - prints Square root: 25.0
-49
Your own throw fires - caught by the first clause
abc
Scanner throws InputMismatchException - caught by the second clause
Figure 1. Three ways out of the try block, one shared finally block.
Algorithm
Start the program and create a Scanner object.
Open a try block and prompt for a number, reading it with nextDouble().
If the number is negative, throw new IllegalArgumentException with an explanatory message.
Otherwise compute Math.sqrt(number) and print the result.
Catch IllegalArgumentException first and print e.getMessage().
Catch Exception next to cover non-numeric input, and print the invalid-input message.
Close the scanner in finally so it happens on every path, then end the program.
Java Program
import java.util.Scanner;
public class SquareRootCalculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
// Taking user input
System.out.print("Enter a number: ");
double number = scanner.nextDouble();
// Handling negative numbers
if (number < 0) {
throw new IllegalArgumentException("Error: Cannot calculate the square root of a negative number.");
}
// Calculating square root
double result = Math.sqrt(number);
System.out.println("Square root: " + result);
} catch (IllegalArgumentException e) {
// Specific clause: must come before catch (Exception e)
System.out.println(e.getMessage());
} catch (Exception e) {
// Reached when Scanner throws InputMismatchException
System.out.println("Error: Invalid input. Please enter a valid number.");
} finally {
scanner.close();
}
}
}
Sample Output
Run 1 - a valid number:
Enter a number: 625
Square root: 25.0
Run 2 - a negative number (your own throw fires):
Enter a number: -49
Error: Cannot calculate the square root of a negative number.
Run 3 - non-numeric input (Scanner throws):
Enter a number: abc
Error: Invalid input. Please enter a valid number.
Lab note:
Swapping the two catch clauses does not just change the message -
it stops the program compiling. Java tests clauses in order, so with
catch (Exception e) first the
IllegalArgumentException clause could never be reached, and the
compiler reports it as unreachable code. Always order catch blocks from the most
specific type to the most general.
Medium Level
ATM Withdrawal System: Custom Exceptions and Multi-Catch
Problem statement: Write a Java program to simulate an ATM
withdrawal system. The program should ask the user to enter their PIN, allow
the withdrawal if the PIN is correct and the balance is sufficient, throw
exceptions for an invalid PIN or insufficient balance, and ensure the system
always shows the remaining balance, even if an exception occurs.
Class Responsibilities
Class
Responsibility
InvalidPinException
Checked exception - extends Exception, passes its message to super
InsufficientBalanceException
Checked exception with the same shape, raised when the amount exceeds the balance
ATMWithdrawalSystem
Holds CORRECT_PIN and balance as static members, runs both checks, handles both exceptions
The Two Validation Gates
Situation
Result
PIN is not 1234
InvalidPinException - the amount is never even asked for
Amount is greater than the balance
InsufficientBalanceException - the balance is left untouched
Something non-numeric is typed
InputMismatchException - handled by the general clause
Every case above, plus success
finally prints Current Balance: and closes the scanner
Figure 2. Two gates in series; whichever one fails, the finally block still reports the balance.
Algorithm
Declare CORRECT_PIN as a static final int and balance as a static double set to 3000.0.
Write InvalidPinException and InsufficientBalanceException, each extending Exception and passing its message to super.
Inside a try block, read the PIN with nextInt().
If the PIN does not match, throw new InvalidPinException - the amount is never requested.
Read the withdrawal amount with nextDouble().
If the amount exceeds the balance, throw new InsufficientBalanceException and leave the balance untouched.
Otherwise subtract the amount and print the success message with the new balance.
Handle both custom types in a single multi-catch clause and print e.getMessage().
Add a general catch (Exception e) for non-numeric input.
In finally, print Current Balance: and close the scanner.
Java Program
import java.util.Scanner;
// Custom exception for invalid PIN
class InvalidPinException extends Exception {
public InvalidPinException(String message) {
super(message); // hand the message to Throwable so getMessage() can return it
}
}
// Custom exception for insufficient balance
class InsufficientBalanceException extends Exception {
public InsufficientBalanceException(String message) {
super(message);
}
}
public class ATMWithdrawalSystem {
private static final int CORRECT_PIN = 1234; // Predefined PIN
private static double balance = 3000.0; // Initial balance
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
// Asking user to enter PIN
System.out.print("Enter PIN: ");
int enteredPin = scanner.nextInt();
// Validate PIN
if (enteredPin != CORRECT_PIN) {
throw new InvalidPinException("Error: Invalid PIN. Please try again.");
}
// Asking user for withdrawal amount
System.out.print("Withdraw Amount: ");
double withdrawAmount = scanner.nextDouble();
// Check for sufficient balance
if (withdrawAmount > balance) {
throw new InsufficientBalanceException("Error: Insufficient balance.");
}
// Perform withdrawal
balance -= withdrawAmount;
System.out.println("Withdrawal Successful! Remaining Balance: " + balance);
} catch (InvalidPinException | InsufficientBalanceException e) {
// Multi-catch: one clause, two unrelated exception types
System.out.println(e.getMessage());
} catch (Exception e) {
System.out.println("Error: Invalid input. Please enter numeric values.");
} finally {
// Runs whether the withdrawal succeeded or an exception was thrown
System.out.println("Current Balance: " + balance);
scanner.close();
}
}
}
Sample Output
Run 1 - correct PIN, sufficient balance:
Enter PIN: 1234
Withdraw Amount: 1200
Withdrawal Successful! Remaining Balance: 1800.0
Current Balance: 1800.0
Run 2 - wrong PIN (gate 1 fails, the amount is never asked for):
Enter PIN: 9999
Error: Invalid PIN. Please try again.
Current Balance: 3000.0
Run 3 - correct PIN, amount larger than the balance:
Enter PIN: 1234
Withdraw Amount: 5000
Error: Insufficient balance.
Current Balance: 3000.0
Why the balance is still correct after a failure:
the subtraction balance -= withdrawAmount sits after the
check, so when the exception is thrown the rest of the try block is
abandoned and the field is never touched. The finally block then
reports the untouched value. Validate first, mutate second - if the order were
reversed, a failed withdrawal would still drain the account.
Hard Level
University Enrolment System: User Input, Capacity, and Prerequisite Checks
Problem statement: Create a Java program for a university
enrolment system with exception handling. The program should read enrolment
requests from the user, throw a CourseFullException when the maximum
enrolment limit is reached, and throw a
PrerequisiteNotMetException when the student has not completed the
prerequisite course.
Class and Method Design
Class / Method
Purpose
CourseFullException(String message)
Checked exception thrown when no seats are left in the course.
PrerequisiteNotMetException(String message)
Checked exception thrown when the student has not completed the required prerequisite.
Student.completeCourse(String courseName)
Adds a completed course to the student's Set<String>.
Student.hasCompletedCourse(String courseName)
Returns true if the prerequisite exists in the completed-course set.
Course.enrollStudent(Student student)
Checks capacity first, checks prerequisite second, then records the enrolment.
readEnrollmentCount(Scanner)
Reads and validates the number of enrolment requests.
readStudent(Scanner, int)
Reads student id, name, and whether the prerequisite course is completed.
attemptEnrollment(Course, Student)
Calls enrollStudent inside try, catches both custom exceptions, and prints seats left in finally.
Exception Triggers
Condition inside enrollStudent
Exception thrown
enrolledStudents.size() >= maxEnrollment
CourseFullException
Prerequisite is set and student.hasCompletedCourse(...) is false
PrerequisiteNotMetException
Both checks pass
No exception; the student is added to the enrolment list.
Flow Chart
Figure 3. User input creates each enrolment request; failed attempts are caught and do not consume a seat.
Algorithm
Define CourseFullException and PrerequisiteNotMetException as checked exceptions by extending Exception.
Create a Student class with student id, name, and a Set<String> for completed courses.
Add completeCourse and hasCompletedCourse methods in Student.
Create a Course class with course code, course name, maximum enrolment limit, prerequisite course, and an enrolled-student list.
Declare enrollStudent(Student student) with throws CourseFullException, PrerequisiteNotMetException.
In enrollStudent, throw CourseFullException if the enrolled-student list has already reached the maximum limit.
If seats are available, check the prerequisite. Throw PrerequisiteNotMetException when the prerequisite is required but absent from the student's completed-course set.
Read the number of enrolment requests from the user.
For each request, read student id, student name, and whether the student has completed the prerequisite course.
Call attemptEnrollment for every student. It calls enrollStudent inside try, catches both custom exceptions in one multi-catch block, and prints available seats in finally.
Close the scanner in finally after all enrolment requests have been processed.
Java Program
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
class CourseFullException extends Exception {
public CourseFullException(String message) {
super(message);
}
}
class PrerequisiteNotMetException extends Exception {
public PrerequisiteNotMetException(String message) {
super(message);
}
}
class Student {
private final String studentId;
private final String name;
private final Set<String> completedCourses;
public Student(String studentId, String name) {
this.studentId = studentId;
this.name = name;
this.completedCourses = new HashSet<>();
}
public void completeCourse(String courseName) {
completedCourses.add(courseName);
}
public boolean hasCompletedCourse(String courseName) {
return completedCourses.contains(courseName);
}
public String getName() {
return name;
}
public String getStudentId() {
return studentId;
}
}
class Course {
private final String courseCode;
private final String courseName;
private final int maxEnrollment;
private final String prerequisiteCourse;
private final List<Student> enrolledStudents;
public Course(String courseCode, String courseName,
int maxEnrollment, String prerequisiteCourse) {
this.courseCode = courseCode;
this.courseName = courseName;
this.maxEnrollment = maxEnrollment;
this.prerequisiteCourse = prerequisiteCourse;
this.enrolledStudents = new ArrayList<>();
}
public void enrollStudent(Student student)
throws CourseFullException, PrerequisiteNotMetException {
if (enrolledStudents.size() >= maxEnrollment) {
throw new CourseFullException(courseCode + " - " + courseName
+ " is full. Maximum enrolment limit is " + maxEnrollment + ".");
}
if (prerequisiteCourse != null
&& !prerequisiteCourse.trim().isEmpty()
&& !student.hasCompletedCourse(prerequisiteCourse)) {
throw new PrerequisiteNotMetException(student.getName()
+ " must complete " + prerequisiteCourse
+ " before enrolling in " + courseName + ".");
}
enrolledStudents.add(student);
System.out.println(student.getName() + " (" + student.getStudentId()
+ ") enrolled in " + courseName + ".");
}
public String getCourseName() {
return courseName;
}
public String getPrerequisiteCourse() {
return prerequisiteCourse;
}
public int getAvailableSeats() {
return maxEnrollment - enrolledStudents.size();
}
}
public class UniversityEnrollmentSystem {
private static int readEnrollmentCount(Scanner scanner) {
while (true) {
System.out.print("How many students want to enroll? ");
try {
int count = Integer.parseInt(scanner.nextLine().trim());
if (count > 0) {
return count;
}
System.out.println("Enter a number greater than zero.");
} catch (NumberFormatException e) {
System.out.println("Enter a valid whole number.");
}
}
}
private static boolean readYesNo(Scanner scanner, String prompt) {
while (true) {
System.out.print(prompt);
String answer = scanner.nextLine().trim().toLowerCase();
if (answer.equals("yes") || answer.equals("y")) {
return true;
}
if (answer.equals("no") || answer.equals("n")) {
return false;
}
System.out.println("Please type yes or no.");
}
}
private static Student readStudent(Scanner scanner, int studentNumber, Course course) {
System.out.println("\nStudent " + studentNumber + " details");
System.out.print("Enter student id: ");
String id = scanner.nextLine().trim();
System.out.print("Enter student name: ");
String name = scanner.nextLine().trim();
Student student = new Student(id, name);
String prerequisite = course.getPrerequisiteCourse();
if (prerequisite != null && !prerequisite.trim().isEmpty()) {
boolean completed = readYesNo(scanner,
"Has the student completed " + prerequisite + "? (yes/no): ");
if (completed) {
student.completeCourse(prerequisite);
}
}
return student;
}
private static void attemptEnrollment(Course course, Student student) {
System.out.println("Trying to enroll " + student.getName()
+ " in " + course.getCourseName());
try {
course.enrollStudent(student);
} catch (CourseFullException | PrerequisiteNotMetException e) {
System.out.println("Enrollment failed: " + e.getMessage());
} finally {
System.out.println("Seats left in " + course.getCourseName()
+ ": " + course.getAvailableSeats());
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Course advancedJava = new Course("CS302", "Advanced Java", 2, "Core Java");
try {
int totalStudents = readEnrollmentCount(scanner);
for (int i = 1; i <= totalStudents; i++) {
Student student = readStudent(scanner, i, advancedJava);
attemptEnrollment(advancedJava, student);
}
} finally {
scanner.close();
}
}
}
Sample Output
How many students want to enroll? 4
Student 1 details
Enter student id: S101
Enter student name: Ananya
Has the student completed Core Java? (yes/no): yes
Trying to enroll Ananya in Advanced Java
Ananya (S101) enrolled in Advanced Java.
Seats left in Advanced Java: 1
Student 2 details
Enter student id: S102
Enter student name: Ravi
Has the student completed Core Java? (yes/no): no
Trying to enroll Ravi in Advanced Java
Enrollment failed: Ravi must complete Core Java before enrolling in Advanced Java.
Seats left in Advanced Java: 1
Student 3 details
Enter student id: S103
Enter student name: Meera
Has the student completed Core Java? (yes/no): yes
Trying to enroll Meera in Advanced Java
Meera (S103) enrolled in Advanced Java.
Seats left in Advanced Java: 0
Student 4 details
Enter student id: S104
Enter student name: Kabir
Has the student completed Core Java? (yes/no): yes
Trying to enroll Kabir in Advanced Java
Enrollment failed: CS302 - Advanced Java is full. Maximum enrolment limit is 2.
Seats left in Advanced Java: 0
Why validation happens before enrolment:
the course list changes only after the capacity and prerequisite checks pass.
If either check throws an exception, control jumps to the matching
catch block and enrolledStudents.add(student) is skipped,
so failed attempts never consume a seat.
Review
Quiz and Viva Questions
1. What is exception handling and what is the need for it?
Exception handling is the mechanism that lets a program respond to a runtime error instead of terminating. Without it, one bad input ends the process and the remaining work - including cleanup - is lost. With it, the error is separated from the normal logic, reported meaningfully, and the program keeps control, as the ATM program does when it still prints the balance after a failed withdrawal.
2. What are the types of exceptions in Java?
Two broad kinds. Error represents serious JVM-level problems such as OutOfMemoryError that a program should not attempt to handle. Exception represents recoverable conditions and splits into checked exceptions (the compiler forces you to handle or declare them) and unchecked exceptions, everything under RuntimeException.
3. What is the difference between throw, throws, and finally?
throw is a statement that raises one exception object at that point in the code. throws is part of a method signature declaring that the method may raise those types, so callers must deal with them. finally is a block attached to try that runs regardless of the outcome and is used for cleanup.
4. What is an abstract class?
A class declared with the abstract keyword. It cannot be instantiated with new and exists only to be extended. It may declare abstract methods - methods with no body that every concrete subclass must implement.
5. Can an abstract class have non-abstract methods?
Yes. An abstract class can mix both, as Animal does in the reading material: animalSound() is abstract and left to the subclass, while sleep() is a fully implemented method that every subclass inherits as-is. It can also have fields and constructors.
6. What is the Throwable hierarchy?
Throwable is the root of everything that can be thrown or caught. Its two direct subclasses are Error and Exception; RuntimeException sits under Exception. Catching Exception therefore catches both checked and unchecked exceptions, but not Error.
7. What is the difference between checked and unchecked exceptions?
A checked exception must be caught or declared with throws, and the compiler enforces this - InvalidPinException and CourseFullException are checked because they extend Exception directly. An unchecked exception extends RuntimeException and carries no compile-time obligation; IllegalArgumentException and InputMismatchException are examples.
8. Does the finally block always run?
Practically always - on normal completion, on a caught exception, on an uncaught one, and even after a return inside the try. The exceptions are calling System.exit(), the thread being killed, or the JVM crashing. That reliability is why scanner.close() belongs there.
9. Can you write try without catch?
Yes, provided there is a finally block: try...finally is legal and is used when you want guaranteed cleanup but intend the exception to propagate to the caller. What is not legal is a bare try block on its own - it needs at least one catch or a finally.
10. What is multi-catch and what are its rules?
Multi-catch handles several exception types in one clause, written catch (A | B e). The types must not be in a subclass relationship with each other, and the parameter e is implicitly final, so it cannot be reassigned inside the block. The ATM program uses it for its two unrelated custom types.
11. Why must catch blocks go from specific to general?
Clauses are tested in the order written and the first matching one wins. If catch (Exception e) came first it would match everything, leaving later clauses unreachable - and Java treats unreachable catch blocks as a compile-time error rather than a warning.
12. How do you create a custom exception, and should it extend Exception or RuntimeException?
Declare a class that extends one of them and give it a constructor taking a message and calling super(message). Extend Exception when the caller can reasonably be expected to recover and you want the compiler to insist on handling - the case for all three custom types in this experiment. Extend RuntimeException for programming errors that should not be routinely caught.
13. What is InputMismatchException and when does Scanner throw it?
It is an unchecked exception in java.util, thrown when the next token does not match the type requested - for example typing abc at a nextDouble() call. Because it is not an IllegalArgumentException, the square root program catches it in the general catch (Exception e) clause.
14. What does getMessage() return, and how does super(message) supply it?
getMessage() returns the detail message string stored inside the Throwable. A custom exception's constructor passes its argument up the chain with super(message), which is where that string is stored - so whatever text you pass to new CourseFullException(...) is exactly what e.getMessage() prints later.