scieee AI-readable full text Open interactive document viewer

Advanced Java Programming

Mr. A. Yasar Arafath MCA.,(PhD)

Abstract

Advanced Java Programming is a comprehensive textbook meticulously designed to take readers beyond the fundamentals of Java and into the world of professional-level, enterprise-grade application development. Written with both clarity and depth, the book explores advanced concepts such as JDBC, Servlets, JSP, JavaFX, Multithreading, RMI, Networking, Hibernate, and the Spring Framework, blending theoretical insights with hands-on coding examples, exercises, and real-world case studies. Each chapter is structured to strengthen analytical and problem-solving skills, helping students, educators, and professionals alike to build robust, secure, and scalable Java applications. Emphasis is placed on design patterns, modular programming, and performance optimization, ensuring that readers develop clean, reusable, and maintainable code. The book also focuses on modern software development practices including Spring Boot, Maven, and MVC architecture, aligning with contemporary industry standards. With its balanced approach to theory and practice, Advanced Java Programming serves as an invaluable reference for learners aiming to master Java for enterprise systems, web technologies, and software engineering.

Full text

Advanced Java Programming Mr. A. Yasar Arafath MCA.,(PhD) Assistant Professor Department of Computer Applications PET Engineering College Vallioor – 627117 Edition Details (I,II,III): I ISBN: 978-93-6786-382-4 Month & Year: October, 2025 Copyright @ Mr. A. Yasar Arafath MCA.,(PhD) Pages: 447 Price: 1000/- About the Author Mr. A. Yasar Arafath is a dedicated academician, researcher, and author with a profound expertise in the cutting-edge domains of Computer Science. Holding both a Bachelor of Computer Applications (BCA) and a Master of Computer Applications (MCA) with first-class distinctions, he has built a robust foundation in theoretical and applied computing. Currently serving as an Assistant Professor in the Department of Computer Applications at PET Engineering College, Vallioor, Mr. Arafath has amassed significant teaching experience, specializing in a wide array of subjects including Advanced Data Structures & Algorithms, Data Structures and Algorithms, Machine Learning, Digital Image Processing, Problem Solving and Programming in C, Basics of Computer Networks, Introduction to Computer Organization and Operating Systems, Object Oriented Software Engineering, Advanced Database Technology, DevOps and Microservices, Database Management System. His pedagogical excellence is demonstrated by the consistently outstanding results produced by his students. His forthcoming book titled, "Advanced Java Programming," marks a significant milestone as his first singleauthored work. This book distills his extensive practical and teaching experience into a comprehensive guide, designed to take readers from fundamental concepts to sophisticated, enterprise-level application development. It reflects his deep-seated commitment to creating impactful educational resources that bridge the gap between academic theory and industry practice. As an active researcher, his intellectual contributions are substantial. He has authored and published influential research papers in reputable international journals on topics such as "Smart Soil Detection with Pseudo RGB Color Matching" and "Image Enhancement using Reduce Haze Algorithm." His innovative spirit is further evidenced by a patented invention in the field of deep learning for image-to-cartoon generation, filed with the Government of India. His passion for knowledge dissemination is also reflected in his previous co-authored books on "Advanced Data Structures and Algorithms" and "Artificial Intelligence and Machine Learning." Committed to lifelong learning, he is an avid participant in the academic community, holding memberships in prestigious organizations like the International Association of Engineers (IAENG) and International Organization for Academic and Scientific Development (IOASD). He continuously enhances his expertise through numerous Faculty Development Programs, workshops, and certifications from leading institutions, focusing on Artificial Intelligence, Data Science and emerging technologies. Mr. Arafath currently pursuing his Ph.D. as a Part-Time Research Scholar at Anna University, Chennai, Mr. A. Yasar Arafath’s research emphasizes Artificial Intelligence applications in Precision Agriculture. His doctoral work integrates Deep Learning, IoT-based sensing, and data analytics to enhance sustainable farming practices and optimize crop productivity. Mr. A. Yasar Arafath stands as a dynamic and knowledgeable voice in computer science education and research, whose written work is deeply informed by both practical industry knowledge and a commitment to academic excellence. Preface The book “Advanced Java Programming” is a comprehensive and detailed guide designed to help readers gain a deep understanding of advanced Java concepts, tools, and frameworks. Java, being one of the most powerful, platform-independent, and versatile programming languages, plays a vital role in enterprise application development, mobile computing, and web-based systems. This book is structured to take learners beyond the fundamentals and into professional-level Java programming, where efficiency, scalability, and security are essential. It begins by revisiting the core concepts of object-oriented programming and gradually progresses into more complex topics such as JDBC, Servlets, JSP, JavaFX, Multithreading, Exception Handling, RMI, and Networking. Readers will also explore advanced frameworks like Hibernate and Spring, which are essential for developing robust, database-driven, and modular enterprise applications. Each chapter combines theoretical explanations with practical examples, coding exercises, and case studies to strengthen problem-solving and analytical skills. The book focuses on building applications that are not only functional but also optimized for performance and maintainability. Emphasis is placed on design patterns, modular programming, and industry-standard practices that help readers adopt a clean, reusable, and scalable coding style. It provides detailed insights into database connectivity, web-based application development, and framework integration, helping learners connect Java concepts with real-world implementations. The content also highlights the importance of security, data handling, and efficient resource management, which are crucial in today’s software industry. Readers will gain exposure to advanced concepts of thread management, synchronization, and exception control, enabling them to design applications capable of handling concurrent operations seamlessly. The book not only strengthens technical proficiency but also cultivates logical thinking and creativity in problem-solving. Each topic has been presented in a clear, concise, and sequential manner to make learning smooth and engaging. By integrating both theoretical and practical approaches, the text ensures that readers can confidently apply what they learn in real-world projects. It is an invaluable resource for students, educators, and professionals aspiring to excel in Java programming. The ultimate goal of this book is to empower readers with the ability to design, develop, and deploy robust, efficient, and industry-ready Java applications, aligning with current technological trends and software development standards. Through continuous learning and hands-on experience, this book inspires readers to master the art and science of advanced Java programming. Mr. A. Yasar Arafath Advanced Java Programming 3 Fig. 1.2 Class Loader Subsystem. Below is an example demonstrating how the JVM loads a class explicitly: Example: class Example { static { System.out.println("Example class has been loaded into the JVM!"); } public void showMessage() { System.out.println("Method of Example class is now running."); } } public class LoaderTest { public static void main(String[] args) throws Exception { System.out.println("Program execution started."); // Explicitly loading the class using Class.forName() Advanced Java Programming 4 Class.forName("Example"); System.out.println("Example class loaded successfully."); // Creating an object and invoking a method Example ex = new Example(); ex.showMessage(); } } Output: Program execution started. Example class has been loaded into the JVM! Example class loaded successfully. Method of Example class is now running. 2. Linking The linking phase is responsible for preparing the loaded class so that it can be executed by the JVM. It ensures that the bytecode is valid, memory is allocated properly, and all references are correctly resolved. The linking process involves three main steps:  Verification: Checks that the bytecode adheres to JVM specifications and is safe to execute.  Preparation: Allocates memory for all static variables and assigns them default values.  Resolution: Replaces symbolic references (names in the bytecode) with direct references to memory locations. 3. Initialization During this phase, the JVM assigns actual values to static variables and executes all static blocks defined within the class. This is the final step before the class is ready for use. Class Loader Types 1. Bootstrap Class Loader: Loads the core Java classes located in the JAVA_HOME/lib directory (e.g., java.lang, java.util). 2. Extension Class Loader: Loads classes from the JAVA_HOME/jre/lib/ext directory, which typically contains extension libraries. 3. System/Application Class Loader: Loads classes from the application's classpath (user-defined classes and libraries). Advanced Java Programming 5 Example: Demonstrating the Class Loader Subsystem public class Demo { public static void main(String[] args) { // String class is loaded by the Bootstrap Class Loader // Bootstrap loader is part of native JVM and not a Java object, so it returns null System.out.println("String class loader: " + String.class.getClassLoader()); // Demo class is loaded by the Application (System) Class Loader System.out.println("Demo class loader: " + Demo.class.getClassLoader()); } } Output: String class loader: null Demo class loader: jdk.internal.loader.ClassLoaders$AppClassLoader@7d4991ad 2. JVM Memory Areas The JVM memory is divided into multiple areas to store various kinds of data during program execution:  Method Area: Contains class-level data such as class names, parent classes, methods, variables, and static information. Shared among all threads.  Heap Area: Used to store all Java objects and arrays. Shared across the entire JVM.  Stack Area: Each thread has its own stack that holds method calls and local variables in stack frames. It is destroyed once the thread terminates.  Program Counter (PC) Register: Maintains the address of the currently executing JVM instruction for each thread.  Native Method Stack: Each thread has a separate stack to handle native (non-Java) method calls executed through JNI. 3. Execution Engine The Execution Engine executes the bytecode (.class file) by reading and processing each instruction using data stored in different memory areas. It consists of three main components:  Interpreter: Reads and executes bytecode instructions one by one. However, it reinterprets methods each time they are called, which can reduce performance.  Just-In-Time (JIT) Compiler: Enhances performance by converting frequently executed bytecode into native machine code, allowing faster execution without reinterpreting. Advanced Java Programming 6  Garbage Collector: Automatically identifies and removes unused or unreferenced objects to free up memory. 4. Java Native Interface (JNI) The JNI acts as a bridge between the JVM and native libraries written in languages like C or C++. It enables Java programs to call native methods and also allows native code to invoke Java methods. This interface is crucial for platform-specific functionality and hardware-level interactions. 5. Native Method Libraries These are collections of precompiled native libraries (often written in C or C++) required for executing native methods through JNI. They allow the JVM to interact directly with the host operating system or hardware to perform low-level tasks efficiently. 1.2 Data Types Java is a statically typed programming language, meaning that the type of each variable is determined at compile time. In Java, the compiler knows the exact type of every variable and ensures that it is used correctly before the program runs. For instance, the statement int x = "GfG"; will cause a compilation error because a string value is being assigned to an integer variable, which is not allowed. Data types in Java come in various sizes and ranges to suit different kinds of data and situations. This design allows developers to efficiently handle diverse data requirements. Categories of Java Data Types Primitive Data Types: These are the most basic data types that store simple values directly in memory. Examples include boolean, char, byte, short, int, long, float, and double. Non-Primitive Data Types (Reference Types): These data types store references (memory addresses) of objects rather than the actual data. Common examples include String, Array, Class, Interface, and Object. Advanced Java Programming 7 Fig. 1.3 Data types in Java. Primitive Data Types Primitive data store only single values and have no additional capabilities. There are 8 primitive data types. They are depicted below in tabular format below as follows: Type Description Defaul t Size Example Literals Range of values boolean true or false false JVMdependent (typically 1 byte) true, false true, false byte 8-bit signed integer 0 1 byte (none) -128 to 127 Advanced Java Programming 8 char Unicode character(16 bit) \u0000 2 bytes 'a', '\u0041', '\101', '\\', '\', '\n', 'β' 0 to 65,535 (unsigned) short 16-bit signed integer 0 2 bytes (none) -32,768 to 32,767 int 32-bit signed integer 0 4 bytes -2,0,1 -2,147,483,648 to 2,147,483,647 long 64-bit signed integer 0L 8 bytes -2L,0L,1L - 9,223,372,036,854,775, 808 to 9,223,372,036,854,775, 807 float 32-bit IEEE 754 floatingpoint 0.0f 4 bytes 3.14f, -1.23e10f ~6-7 significant decimal digits double 64-bit IEEE 754 floatingpoint 0.0d 8 bytes 3.1415d, 1.23e100d ~15-16 significant decimal digits Table. 1.1 Primitive Data Types. 1. boolean Data Type The boolean data type represents a logical value that can either be true or false. Conceptually, it stores a single bit of information, though in practice, the actual size used by the Java Virtual Machine (JVM) depends on the implementation (typically one byte or 8 bits). Boolean values cannot be implicitly or explicitly converted to other data types using casts, though manual conversions can be implemented if needed. Syntax: boolean flag; Size: Virtual machine dependent (typically 1 byte or 8 bits) Example: The following example shows how to use the boolean data type to represent logical conditions. // Demonstrating boolean data type public class BooleanExample { Advanced Java Programming 9 public static void main(String[] args) { boolean isRaining = false; boolean isSunny = true; System.out.println("Is it raining today? " + isRaining); System.out.println("Is it sunny today? " + isSunny); } } Output: Is it raining today? false Is it sunny today? true 2. byte Data Type The byte data type is an 8-bit signed two’s complement integer. It is mainly used to save memory when dealing with large arrays of small numerical values, especially when data values fall within the byte range. Syntax: byte smallNum; Size: 1 byte (8 bits) Example: This example demonstrates how to use the byte data type to store and display small integer values. // Demonstrating byte data type public class ByteExample { public static void main(String[] args) { byte speed = 60; byte temperature = -5; System.out.println("Vehicle speed: " + speed + " km/h"); System.out.println("Room temperature: " + temperature + "°C"); } } Output: Vehicle speed: 60 km/h Room temperature: -5°C Advanced Java Programming 10 3. short Data Type The short data type is a 16-bit signed two’s complement integer. It is often used when memory savings are necessary, such as in large data arrays, and when the values fit within the short range. Syntax: short shortVar; Size: 2 bytes (16 bits) Example: The following program demonstrates how to use the short data type to represent moderately small integer values. // Demonstrating short data type public class ShortExample { public static void main(String[] args) { short population = 15000; short temperature = -15; System.out.println("Village population: " + population); System.out.println("Winter temperature: " + temperature + "°C"); } } Output: Village population: 15000 Winter temperature: -15°C 4. int Data Type The int data type represents a 32-bit signed two’s complement integer. It is the most commonly used integer type in Java for handling whole numbers. Syntax: int intVar; Size: 4 bytes (32 bits) Example: This example demonstrates how to use the int data type to display large integer values. Advanced Java Programming 11 // Demonstrating int data type public class IntExample { public static void main(String[] args) { int population = 5000000; int distance = 120000000; System.out.println("City Population: " + population); System.out.println("Distance to Sun (in km): " + distance); } } Output: City Population: 5000000 Distance to Sun (in km): 120000000 5. long Data Type The long data type is a 64-bit signed two’s complement integer, used when an int is not sufficient to hold very large values. Syntax: long longVar; Size: 8 bytes (64 bits) Note: Since Java SE 8, long can also represent an unsigned 64-bit value, ranging from 0 to 264−12^{64} - 1264−1. The Long class includes utility methods for unsigned arithmetic such as compareUnsigned() and divideUnsigned(). Example: The following program demonstrates how to use the long data type to handle very large numbers. // Demonstrating long data type public class LongExample { public static void main(String[] args) { long stars = 9500000000L; long galaxyDistance = 120000000000000L; System.out.println("Number of Stars: " + stars); System.out.println("Galaxy Distance (in light years): " + galaxyDistance); } } Advanced Java Programming 12 Output: Number of Stars: 9500000000 Galaxy Distance (in light years): 120000000000000 6. float Data Type The float data type is a single-precision 32-bit IEEE 754 floating-point number. It’s useful for saving memory in large arrays of floating-point data when precision is not critical. Syntax: float floatVar; Size: 4 bytes (32 bits) Example: This example shows how to use the float data type to represent decimal values. // Demonstrating float data type public class FloatExample { public static void main(String[] args) { float interestRate = 7.5f; float temperature = 36.6f; System.out.println("Bank Interest Rate: " + interestRate + "%"); System.out.println("Body Temperature: " + temperature + "°C"); } } Output: Bank Interest Rate: 7.5% Body Temperature: 36.6°C 7. double Data Type The double data type represents a double-precision 64-bit IEEE 754 floating-point number. It is generally the default choice for representing decimal or fractional values in Java. Syntax: double doubleVar; Advanced Java Programming 19 Variable Declaration and Initialization Variables must be declared before they can be used.A variable declaration includes the data type followed by the variable name.To assign a value, use the assignment (=) operator. Each statement ends with a semicolon (;). Syntax: data_type variable_name [= value] [, variable_name [= value]] ...; Here,  data_type specifies the kind of value the variable can hold.  variable_name is the name given to the variable.  Multiple variables of the same type can be declared in one line using commas. Examples of Valid Variable Declarations and Initializations int x, y, z; // Declares three integer variables int a = 5, b = 15; // Declares and initializes two integers byte code = 20; // Declares and initializes a byte variable double pi = 3.14159; // Declares and initializes a double char grade = 'A'; // Declares and initializes a char variabl Types of Variables in Java Java supports three main types of variables: 1. Local Variables 2. Instance Variables 3. Class (Static) Variables 1. Local Variables  Declared inside a method, constructor, or block.  Created when the method is invoked and destroyed when it exits.  Access modifiers (like public or private) cannot be used.  Only visible within their scope (method, constructor, or block).  Stored in the stack memory.  Have no default value must be initialized before use. Example 1: Local Variable with Initialization public class DogAge { Advanced Java Programming 20 public void puppyAge() { int age = 2; // Local variable initialized age = age + 5; System.out.println("Puppy age is: " + age); } public static void main(String[] args) { DogAge dog = new DogAge(); dog.puppyAge(); } } Output: Puppy age is: 7 Example 2: Local Variable without Initialization public class DogAgeError { public void puppyAge() { int age; // Declared but not initialized age = age + 3; // Compilation error System.out.println("Puppy age is: " + age); } public static void main(String[] args) { DogAgeError dog = new DogAgeError(); dog.puppyAge(); } } Output (Compilation Error): DogAgeError.java:4: variable age might not have been initialized age = age + 3; ^ 1 error 2. Instance Variables  Declared inside a class but outside any method, constructor, or block.  Each object of the class gets its own copy of instance variables.  Created when an object is instantiated using new and destroyed when the object is destroyed.  Used to represent object properties or states. Advanced Java Programming 21  Can have access modifiers (public, private, protected).  Have default values (0 for numbers, false for boolean, and null for objects).  Accessed using either the object reference or directly inside non-static methods. Example: Java Instance Variables public class Student { // Instance variables public String name; // Visible to other classes private double marks; // Private variable visible only within this class // Constructor to assign name public Student(String studentName) { name = studentName; } // Setter method for marks public void setMarks(double studentMarks) { marks = studentMarks; } // Method to print student details public void printDetails() { System.out.println("Student Name: " + name); System.out.println("Marks: " + marks); } public static void main(String[] args) { Student student1 = new Student("Priya"); student1.setMarks(92.5); student1.printDetails(); } } Output: Student Name: Priya Marks: 92.5 3. Class (Static) Variables  Declared with the static keyword inside a class but outside any method, constructor, or block.  There is only one copy of a static variable for the entire class, shared by all objects.  Typically used for constants or shared properties. Advanced Java Programming 22  Stored in static memory, created when the program starts and destroyed when it ends.  Usually declared as public static final for constants.  Accessed using the class name (e.g., ClassName.variableName).  Default values are the same as instance variables. Example: Java Static Variables public class Company { // Static variable private static double bonusPercentage; // Constant public static final String DEPARTMENT = "Human Resources"; public static void main(String[] args) { bonusPercentage = 12.5; System.out.println(DEPARTMENT + " department bonus: " + bonusPercentage + "%"); } } Output: Human Resources department bonus: 12.5% Note: If a static variable or constant is accessed from another class, it must be referenced using the class name, e.g., Company.DEPARTMENT. Variable Type Declared In Memory Location Default Value Access Modifiers Allowed Scope Local Variable Inside methods, constructors, or blocks Stack None (must initialize) No Only within the block/method Instance Variable Inside class but outside methods Heap Yes (depends on type) Yes Throughout the class Static Variable Inside class with static keyword Static memory Yes (depends on type) Yes Shared among all instances Table. 1.3 Summary of Variables. Advanced Java Programming 23 Keywords Keywords are reserved words that have predefined meanings and are recognized by the Java compiler for specific internal processes or predefined actions. These words cannot be used as identifiers (i.e., variable names, method names, class names, or object names) because they are part of the language syntax. Example: Demonstrating Java Keywords // Java Program to demonstrate the use of keywords class KeywordExample { public static void main(String[] args) { // Using final and int keywords final int score = 75; // Using if and else keywords if (score >= 50) { System.out.println("Passed the test successfully!"); } else { System.out.println("Failed to pass the test."); } } } Output: Passed the test successfully! What Happens If We Use a Variable Name Same as a Keyword? If you try to use a keyword as a variable name, the Java compiler will throw an error because keywords are reserved and cannot be redefined or repurposed. Example: Attempting to Use a Keyword as a Variable Name // Java Program to illustrate using a keyword as a variable name class InvalidKeywordExample { public static void main(String[] args) { // The word "class" is a reserved keyword in Java String class = "Hello, Java!"; System.out.println(class); } } Advanced Java Programming 24 Output (Compilation Error): InvalidKeywordExample.java:5: error: not a statement String class = "Hello, Java!"; ^ InvalidKeywordExample.java:5: error: ';' expected String class = "Hello, Java!"; ^ 2 errors Important Points About Java Keywords  The keywords const and goto are reserved in Java but not currently used.  The words true, false, and null are literals, not actual keywords however, they cannot be used as identifiers.  Java keywords are case-sensitive using uppercase (like IF instead of if) will result in a compilation error. Java Keywords List As of Java 21, there are 53 keywords in the Java programming language. Most IDEs highlight these words in a different color to differentiate them from user-defined identifiers. Below is a list of all Java keywords and their primary purpose: Keyword Usage / Description abstract Specifies that a class or method must be implemented later in a subclass. assert Used to test assumptions during program execution for debugging. boolean Data type representing true or false values. break Exits a loop or switch statement. byte Data type for 8-bit signed integers. case Defines individual branches within a switch statement. catch Handles exceptions thrown by try blocks. char Data type for 16-bit Unicode characters. class Declares a new class. const Reserved but not used in Java. continue Skips the current iteration of a loop and moves to the next. default Specifies the default case in a switch statement. do Begins a do-while loop. double Data type for 64-bit floating-point numbers. else Defines an alternative branch in an if statement. enum Declares an enumerated type (set of named constants). Advanced Java Programming 25 extends Indicates that a class inherits from another class. final Declares constants, prevents method overriding, or inheritance. finally Defines a block of code that always executes after a try-catch. float Data type for 32-bit floating-point numbers. for Begins a for loop. goto Reserved but not used. if Tests a condition and executes code accordingly. implements Indicates that a class implements an interface. import Includes other Java classes or packages. instanceof Tests whether an object is an instance of a particular class. int Data type for 32-bit signed integers. interface Declares an interface (a collection of abstract methods). long Data type for 64-bit signed integers. native Specifies that a method is implemented in native code (e.g., C/C++). new Creates new objects. null Represents a reference that points to no object. package Declares a namespace for classes. private Access modifier that restricts access to within the same class. protected Access modifier allowing access within subclasses or same package. public Access modifier making members visible everywhere. return Exits a method and optionally returns a value. short Data type for 16-bit signed integers. static Defines class-level variables or methods. strictfp Ensures consistent floating-point calculations across platforms. super Refers to the parent class (used for inheritance and constructors). switch Executes one block of code among many options based on a test value. synchronized Used to handle thread synchronization in multithreading. this Refers to the current object in a class. throw Creates (throws) an exception manually. throws Declares exceptions a method might throw. transient Marks variables not to be serialized. try Starts a block of code that may throw exceptions. void Specifies that a method does not return a value. volatile Indicates that a variable may be modified unexpectedly (used in threads). while Begins a while loop. sealed Restricts which other classes can extend this class. permits Used in sealed classes to define which subclasses are allowed. Advanced Java Programming 26 Example: Demonstrating sealed and permits (Java 21 Feature) // Demonstrating the use of sealed and permits keywords sealed class Shape permits Circle, Square { } final class Circle extends Shape { void area() { System.out.println("Area of Circle = πr²"); } } final class Square extends Shape { void area() { System.out.println("Area of Square = side × side"); } } public class SealedExample { public static void main(String[] args) { Circle c = new Circle(); Square s = new Square(); c.area(); s.area(); } } Output: Area of Circle = πr² Area of Square = side × side 1.4 Operators Operators are special symbols used to perform specific operations on variables or values. They play a crucial role in programming by enabling efficient data manipulation and computation. Operators are special symbols used to perform specific operations on variables and values. They are fundamental in programming as they allow developers to perform arithmetic, logical, and comparison-based operations efficiently on data. Advanced Java Programming 27 1. Arithmetic Operators Arithmetic Operators are used to perform basic mathematical calculations such as addition, subtraction, multiplication, division, and modulus on numeric data types. // Demonstrating Arithmetic Operators public class ArithmeticExample { public static void main(String[] args) { int x = 15, y = 4; int sum = x + y; // Addition int diff = x - y; // Subtraction int product = x * y; // Multiplication int div = x / y; // Division int mod = x % y; // Modulus (remainder) System.out.println("Sum: " + sum); System.out.println("Difference: " + diff); System.out.println("Product: " + product); System.out.println("Division: " + div); System.out.println("Modulus: " + mod); } } Output: Sum: 19 Difference: 11 Product: 60 Division: 3 Modulus: 3 2. Unary Operators Unary Operators operate on a single operand. They are mainly used for incrementing, decrementing, or negating values. // Demonstrating Unary Operators public class UnaryExample { public static void main(String[] args) { int num1 = 7; int num2 = 7; System.out.println("Post-increment: " + (num1++)); Advanced Java Programming 28 System.out.println("Pre-increment: " + (++num1)); System.out.println("Post-decrement: " + (num2--)); System.out.println("Pre-decrement: " + (--num2)); } } Output: Post-increment: 7 Pre-increment: 9 Post-decrement: 7 Pre-decrement: 5 3. Assignment Operators The Assignment Operator (=) assigns values from the right-hand side to the left-hand variable. Compound assignment operators (+=, -=, *=, /=, %=) combine arithmetic with assignment for cleaner code. // Demonstrating Assignment Operators public class AssignmentExample { public static void main(String[] args) { int n = 8; n += 4; // n = n + 4 System.out.println("After += : " + n); n *= 3; // n = n * 3 System.out.println("After *= : " + n); n -= 6; // n = n - 6 System.out.println("After -= : " + n); n /= 2; // n = n / 2 System.out.println("After /= : " + n); n %= 5; // n = n % 5 System.out.println("After %= : " + n); } } Advanced Java Programming 35 Syntax: operand1 operator operand2 Example: public class ArithmeticExpression { public static void main(String[] args) { int a = 20; int b = 4; int result = a + b * 2; System.out.println("Result: " + result); } } Output: Result: 28 Explanation: According to operator precedence, multiplication (b * 2 = 8) is performed first, then addition (20 + 8 = 28). c) Relational Expression A relational expression compares two values and produces a boolean (true or false). Syntax: operand1 relational_operator operand2 Example: public class RelationalExpression { public static void main(String[] args) { int age = 21; boolean eligible = age >= 18; System.out.println("Eligible to vote: " + eligible); } } Output: Eligible to vote: true Explanation: The expression age >= 18 evaluates to true because 21 is greater than 18. Advanced Java Programming 36 d) Logical Expression A logical expression combines multiple conditions and returns a boolean result. These are widely used in decision-making statements like if and while. Syntax: condition1 logical_operator condition2 Example: public class LogicalExpression { public static void main(String[] args) { int marks = 75; boolean pass = (marks >= 50) && (marks <= 100); System.out.println("Pass Status: " + pass); } } Output: Pass Status: true Explanation: Both conditions (marks >= 50) and (marks <= 100) are true, so the && (logical AND) operator returns true. e) Method Invocation Expression A method invocation expression calls a method and uses its return value as part of the computation. Example: public class MethodExpression { public static void main(String[] args) { String text = "Programming"; int length = text.length(); // returns 11 System.out.println("Length: " + length); } } Output: Length: 11 Advanced Java Programming 37 Explanation: The length() method returns the number of characters in the string "Programming", which is 11. f) Complex Expression A complex expression combines literals, variables, operators, and method calls into one expression. It’s evaluated according to Java’s operator precedence and associativity rules. Example: public class ComplexExpression { public static void main(String[] args) { int a = 10, b = 5; String name = "Java"; int result = (a + b) / 3 + name.length(); System.out.println("Final Result: " + result); } } Output: Final Result: 9 Explanation: 1. (a + b) → 15 2. (a + b) / 3 → 5 3. name.length() → 4 4. 5 + 4 → 9 So the final result of the complex expression is 9. Evaluation of Expressions Expressions in Java are evaluated based on operator precedence and associativity rules. This determines the order in which operations are performed when an expression has multiple operators. For example: int value = 5 + 3 * 2; System.out.println(value); Advanced Java Programming 38 Output: 11 Explanation: Multiplication (3 * 2 = 6) has higher precedence than addition, so it’s evaluated first. Then 5 + 6 = 11. If you want to change the order of execution, use parentheses: int value = (5 + 3) * 2; // Parentheses take precedence System.out.println(value); Output: 16 Characteristics of Java Expressions  Every expression evaluates to a single value.  Expressions can be nested inside other expressions.  Type conversion (casting) may occur automatically or explicitly during evaluation.  They follow strict data type rules type mismatches cause compile-time errors.  Expressions can have side effects, like modifying variables (e.g., x++) Control Statements Control statements are instructions that regulate the flow of a program’s execution based on specific conditions. They are used to make decisions, repeatedly execute blocks of code, or transfer control to different parts of the program as needed. Control statements are an essential feature of Java and other programming languages, allowing developers to create flexible, logical, and interactive programs. Types of Control Statements 1. Decision-Making Statements 2. Looping Statements 3. Jump Statements Advanced Java Programming 39 Decision-Making Statements Flowchart Fig. 1.4 Flow chart of if Statement. Decision-making statements in Java are control structures that determine the flow of program execution based on specific conditions. They enable the program to choose different paths depending on whether a given condition (or set of conditions) evaluates to true or false. In Java, the primary types of decision-making statements are:  if statement  if-else statement  switch statement ‘if’ Statement  The if statement in Java is used to evaluate a boolean condition.  If the condition evaluates to true, the block of code inside the if statement is executed. Advanced Java Programming 40  It represents the simplest form of decision-making, allowing selective execution of code depending on the condition’s result. Syntax: if (condition) { // Code to execute if the condition is true } Example: Online Shopping Discount System A user makes a purchase worth a certain amount. The system checks if the user is eligible for a discount (for example, purchases above Rs.1000 get a 10% discount). public class ShoppingDiscount { public static void main(String[] args) { int totalPurchase = 1200; // Total purchase amount in Rupees double discount = 0.0; // Discount value if (totalPurchase >= 1000) { discount = totalPurchase * 0.10; // 10% discount totalPurchase -= discount; System.out.println("Congratulations! You got a 10% discount."); } System.out.println("Final amount to pay: Rs. " + totalPurchase); } } Output Congratulations! You got a 10% discount. Final amount to pay: Rs. 1080.0 'if-else' Statement The if-else statement in Java is used to handle two possible conditions in a program. It follows an if statement and provides an alternative set of actions when the if condition evaluates to false. If the if condition is true, the corresponding code block executes; otherwise, the code inside the else block runs. Syntax: if (condition) { // Code to execute when the condition is true Advanced Java Programming 41 } else { // Code to execute when the condition is false } Flowchart Fig. 1.5 Flowchart of if else statement. Example: ATM Withdrawal System A user tries to withdraw money from an ATM.  If the account balance is greater than or equal to the withdrawal amount, the transaction is successful.  If the balance is lower but still above Rs.100, the system suggests withdrawing a smaller amount.  If the balance is too low, it notifies the user about insufficient funds. Advanced Java Programming 42 public class ATMWithdrawal { public static void main(String[] args) { int accountBalance = 1500; // User's account balance in Rupees int withdrawAmount = 2000; // Desired withdrawal amount int smallerWithdraw = 500; // Suggested smaller withdrawal amount if (accountBalance >= withdrawAmount) { accountBalance -= withdrawAmount; System.out.println("Withdrawal successful. Remaining balance: Rs. " + accountBalance); } else if (accountBalance >= smallerWithdraw) { System.out.println("Insufficient balance for Rs. 2000 withdrawal. You can withdraw Rs. 500 instead."); } else { System.out.println("Insufficient balance to perform any withdrawal."); } } } Output: Insufficient balance for Rs. 2000 withdrawal. You can withdraw Rs. 500 instead. 'switch' Statement The switch statement in Java is used to execute one block of code among multiple options based on the value of a variable or expression. It provides a cleaner and more structured alternative to using multiple if-else statements, making the code easier to read and maintain. Syntax switch (expression) { case value1: // Code to execute if expression equals value1 break; case value2: // Code to execute if expression equals value2 break; // ... default: // Code to execute if expression does not match any case } Advanced Java Programming 43 Fig. 1.6 Flowchart of Switch stement. Example:Online Movie Ticket Booking System A user selects a movie category Standard, Premium, or VIP. Each category has a different ticket price. The system calculates the total amount to be paid based on the selected category. (Here, category is set to "Premium".) public class MovieTicketBooking { public static void main(String[] args) { String category = "Premium"; // User’s selected movie category int ticketPrice; Advanced Java Programming 44 switch (category) { case "Standard": ticketPrice = 150; // Price for Standard category break; case "Premium": ticketPrice = 250; // Price for Premium category break; case "VIP": ticketPrice = 400; // Price for VIP category break; default: ticketPrice = 0; // Invalid category System.out.println("Invalid movie category selected."); } if (ticketPrice != 0) { System.out.println("Total ticket cost for " + category + " category: Rs. " + ticketPrice); } } } Output: Total ticket cost for Premium category: Rs. 250 2. Looping Statements Looping statements in Java are used to execute a block of code repeatedly as long as a specified condition is true. They are essential for performing repetitive tasks such as traversing arrays, processing data, and automating repeated actions. The main types of looping statements in Java are:  for loop  while loop  do-while loop 'for' Loop Example: Calculating the Sum of Even Numbers Let's write a program to calculate the sum of all even numbers between 1 and 50. public class SumOfEvenNumbers { public static void main(String[] args) { Advanced Java Programming 51 Syntax break; Fig. 1.10 Flowchart of Break statement. Example: Stop Printing When a Specific Word is Found public class StopAtWord { public static void main(String[] args) { String[] words = {"apple", "banana", "grape", "stop", "mango"}; for (String word : words) { if (word.equals("stop")) { System.out.println("Stopping at the word 'stop'."); break; } System.out.println(word); } } } Advanced Java Programming 52 Output: apple banana grape Stopping at the word 'stop'. 'continue' Statement The continue statement skips the current iteration of a loop and proceeds with the next one. It’s often used when certain conditions need to be ignored. Syntax continue; Fig. 1.11 Flowchart of Control Statement. Example: Print Odd Numbers Only public class PrintOddNumbers { public static void main(String[] args) { for (int i = 1; i <= 20; i++) { if (i % 2 == 0) { continue; // Skip even numbers } System.out.println(i); } Advanced Java Programming 53 } } Output: 1 3 5 7 9 11 13 15 17 19 1.6 Classes , Objects and Constructors Classes A class is a blueprint or template from which individual objects are created. It defines the data (attributes) and behavior (methods) that its objects will have. In Java, everything revolves around classes and objects. For instance, if you want to represent cars in a program, the class will be Car, and each car (like car1, car2, etc.) will be an object of that class. Properties of Java Classes  A class does not take up any memory space until an object is created.  It acts as a blueprint for real-world entities but is not itself a real-world object.  A class mainly contains data members (variables) and methods (functions).  Classes can also be nested inside other classes.  Classes follow all OOP principles such as inheritance, encapsulation, and abstraction. Advanced Java Programming 54 Types of Class Variables A class can contain three main types of variables: 1. Local Variables Defined inside methods, constructors, or blocks. They are created when the method starts and destroyed when the method ends. 2. Instance Variables Defined inside a class but outside any method. They are created when the class is instantiated (object created). 3. Class Variables (Static Variables) Declared inside the class but outside any method, and marked with the static keyword. They are shared by all objects of the class. Creating (Declaring) a Java Class You can declare a class using an access modifier, followed by the class keyword and the class name. Syntax: access_modifier class ClassName { data members; constructors; methods; } Example of a Java Class Let’s create a class named Car. The class attributes are brand, year, and color. The methods are setBrand(), setYear(), setColor(), and displayDetails(). // Creating a Java class class Car { // Declaring and initializing attributes String brand; int year; String color; Advanced Java Programming 55 // Methods to set brand, year, and color public void setBrand(String brand) { this.brand = brand; } public void setYear(int year) { this.year = year; } public void setColor(String color) { this.color = color; } // Method to display car details public void displayDetails() { System.out.println("Car Details:"); System.out.println("Brand: " + this.brand); System.out.println("Year: " + this.year); System.out.println("Color: " + this.color); } } Objects An object is an instance of a class.It represents a real-world entity that has state (data) and behavior (methods). For example, a Car object’s state could be its brand, color, and year, while its behavior could be starting, driving, or stopping. Creating (Declaring) a Java Object A class provides the blueprint, and an object is created from it using the new keyword. Three steps for object creation: 1. Declaration – Defining a variable with an object type. 2. Instantiation – Using new to create the object. 3. Initialization – Calling a constructor to set up the object. Syntax: ClassName objectName = new ClassName([parameters]); Advanced Java Programming 56 Example: Creating and Using an Object public class Main { public static void main(String[] args) { // Creating an object of Car class Car myCar = new Car(); // Setting attributes myCar.setBrand("Tesla"); myCar.setYear(2023); myCar.setColor("Red"); // Displaying details myCar.displayDetails(); } } Output: Car Details: Brand: Tesla Year: 2023 Color: Red Accessing Instance Variables and Methods Instance variables and methods are accessed through objects. Syntax: // Create an object ObjectReference = new ClassName(); // Access instance variable ObjectReference.variableName; // Access instance method ObjectReference.methodName(); Example: Here’s another example using a Book class. public class Book { int pages; Advanced Java Programming 57 // Constructor public Book(String title) { System.out.println("Book title: " + title); } // Setter method public void setPages(int pages) { this.pages = pages; } // Getter method public int getPages() { System.out.println("Number of pages: " + pages); return pages; } public static void main(String[] args) { // Object creation Book myBook = new Book("Java Programming"); // Set and get page count myBook.setPages(450); myBook.getPages(); // Access variable directly System.out.println("Direct access: " + myBook.pages); } } Output: Book title: Java Programming Number of pages: 450 Direct access: 450 Rules for Using Classes and Objects 1. Only one public class can exist per source file. 2. The filename must match the public class name, followed by .java. Example: public class Student {} - File name: Student.java 3. Multiple non-public classes can exist in one file. 4. If a package is used, it must appear as the first statement in the file. 5. Import statements (if any) must appear after the package declaration. 6. Import and package statements apply to all classes in the file. 7. Classes can be abstract, final, or nested, depending on usage. Advanced Java Programming 58 Another Example: Laptop Class class Laptop { String brand; int ram; double price; // Constructor public Laptop(String brand, int ram, double price) { this.brand = brand; this.ram = ram; this.price = price; } // Display details public void showDetails() { System.out.println("Laptop Details:"); System.out.println("Brand: " + brand); System.out.println("RAM: " + ram + "GB"); System.out.println("Price: Rs. " + price); } public static void main(String[] args) { Laptop l1 = new Laptop("HP", 16, 85000); l1.showDetails(); } } Output: Laptop Details: Brand: HP RAM: 16GB Price: Rs. 85000 Constructors Constructors are special methods used to initialize an object when it is created. A constructor has the same name as the class and looks similar to a method, but it does not have any return type, not even void. Typically, constructors are used to set initial values for instance variables or perform any setup tasks needed when an object is first created. Advanced Java Programming 59 Every class in Java has at least one constructor. If you do not explicitly define one, Java automatically provides a default constructor that initializes all instance variables to their default values (like 0, null, or false). However, once you create your own constructor, the default one is no longer provided automatically. Rules for Creating Java Constructors When defining constructors in Java, keep these rules in mind: 1. The constructor’s name must match the class name. 2. Constructors do not have a return type (not even void). 3. You can define multiple constructors with different parameter lists this is called constructor overloading. 4. You can use access modifiers (public, private, etc.) to control the visibility of constructors. 5. If no constructor is explicitly defined, Java automatically provides a default constructor. Creating a Java Constructor To create a constructor, write the class name followed by parentheses () and define the body inside curly braces {}. Syntax: class ClassName { ClassName() { // Constructor body } } Example: Simple Constructor Here’s a simple example where a constructor prints a message when an object is created: public class Greeting { // Constructor Greeting() { System.out.println("Welcome to Java Programming!"); } public static void main(String[] args) { System.out.println("Program started..."); Advanced Java Programming 60 // Creating an object - constructor is automatically called Greeting message = new Greeting(); } } Output: Program started... Welcome to Java Programming! Types of Constructors in Java Java provides three main types of constructors: 1. Default Constructor 2. No-Argument Constructor 3. Parameterized Constructor 1. Default Constructor If you don’t define any constructor in your class, Java automatically provides a default constructor that initializes all variables with default values. Example: Default Constructor public class Car { String brand; int year; public static void main(String[] args) { // Default constructor will be invoked automatically Car myCar = new Car(); System.out.println("Brand: " + myCar.brand); System.out.println("Year: " + myCar.year); } } Output: Brand: null Year: 0 Here, since we didn’t define any constructor, Java automatically created a default one and assigned the default values (null for String and 0 for int). Advanced Java Programming 67 Advantages of Access Control 1. Data Security: Access control prevents unauthorized access and modification of private data, protecting the integrity of objects. 2. Encapsulation: It supports data hiding, ensuring that only relevant data and methods are exposed to the user. 3. Code Maintenance: By separating public interfaces from internal details, programs become easier to understand, update, and debug. 4. Error Reduction: It minimizes accidental interference between unrelated parts of code. 5. API Design: Access control allows developers to clearly define which parts of a program are intended for public use and which are internal. 1.7 Method Overloading Method overloading in Java is a feature that allows a class to have multiple methods with the same name but different parameter lists. It is one of the ways Java implements compile-time polymorphism (also known as static polymorphism). This means that the method to be executed is determined by the compiler at compile time, based on the number, type, and order of arguments passed during the method call. Method overloading makes the code more readable, flexible, and maintainable, as the same method name can be reused for similar types of tasks that differ only in their input parameters. Key Characteristics of Method Overloading 1. Same method name, different parameters: Overloaded methods must have the same name but a different number, type, or order of parameters. 2. Return type alone doesn’t matter: You cannot overload methods by changing only the return type there must be a difference in the parameter list. 3. Compile-time decision: The Java compiler determines which version of the method to call based on the arguments provided. 4. Increased readability: Overloading allows developers to use one method name for conceptually similar operations. Ways to Achieve Method Overloading Method overloading can be achieved in three main ways: 1. Changing the number of parameters 2. Changing the data type of parameters Advanced Java Programming 68 3. Changing the order of parameters 1. Changing the Number of Parameters One of the most common ways to achieve method overloading is by defining multiple methods with the same name but a different number of parameters. In this approach, the compiler distinguishes between methods by the count of arguments passed during the method call. Example: public class Calculator { // Method with two parameters public int add(int a, int b) { return a + b; } // Overloaded method with three parameters public int add(int a, int b, int c) { return a + b + c; } } public class Main { public static void main(String[] args) { Calculator calc = new Calculator(); // Calling method with two arguments System.out.println("Sum of two numbers: " + calc.add(10, 20)); // Calling method with three arguments System.out.println("Sum of three numbers: " + calc.add(5, 10, 15)); } } Output: Sum of two numbers: 30 Sum of three numbers: 30 Explanation: Both methods are named add(), but the compiler differentiates between them based on the number of parameters passed. When two arguments are given, the first method runs; when three are given, the second one runs. Advanced Java Programming 69 2. Changing the Data Type of Parameters Method overloading can also be achieved by changing the data types of the parameters in the method definition. This allows a single method name to handle various types of inputs for example, integers, doubles, or strings depending on what the user passes. Example: public class Display { // Method with integer parameter public void show(int number) { System.out.println("Integer value: " + number); } // Overloaded method with string parameter public void show(String message) { System.out.println("String message: " + message); } // Overloaded method with double parameter public void show(double value) { System.out.println("Double value: " + value); } } public class Main { public static void main(String[] args) { Display obj = new Display(); obj.show(25); // Calls method with int obj.show("Hello Java"); // Calls method with String obj.show(19.99); // Calls method with double } } Output: Integer value: 25 String message: Hello Java Double value: 19.99 Advanced Java Programming 70 Explanation: All three methods have the same name show(), but each handles a different data type. The compiler automatically calls the method whose parameter type matches the data type of the argument passed. 3. Changing the Order of Parameters Method overloading can also occur when methods have the same name and the same number of parameters but the order of parameter types differs. This technique is useful when you need to pass mixed data types in different sequences. Example: public class Person { // Method with String followed by int public void details(String name, int age) { System.out.println("Name: " + name + ", Age: " + age); } // Overloaded method with int followed by String public void details(int age, String name) { System.out.println("Age: " + age + ", Name: " + name); } } public class Main { public static void main(String[] args) { Person p = new Person(); p.details("Riya", 22); // Calls first method p.details(25, "Karan"); // Calls second method } } Output: Name: Riya, Age: 22 Age: 25, Name: Karan Explanation: Here, both methods are named details() and have the same number of parameters but in different orders. The compiler identifies which one to call based on the sequence of the arguments provided. Advanced Java Programming 71 Rules for Method Overloading 1. Same name: All overloaded methods must have the same method name. 2. Different parameters: Methods must differ in the number, type, or order of parameters. 3. Return type does not matter: Changing the return type alone will not constitute overloading. 4. Compile-time binding: The method call is resolved by the compiler, not at runtime. 5. Access modifiers: Overloaded methods can have different access modifiers (e.g., one public, another private), though it’s not common. Advantages of Method Overloading 1. Improves code readability: You can use the same method name for similar operations. 2. Enhances reusability: The same method name can work with different data types or parameter lists. 3. Simplifies maintenance: Reduces the number of method names you need to remember. 4. Supports compile-time polymorphism: The compiler decides which method to invoke, ensuring efficient execution. Real-World Example: Method Overloading in Action Consider an application that calculates area of different shapes a circle, a rectangle, and a triangle. Each method performs a similar operation (calculating area) but requires different parameters. Example: public class Shape { // Area of a circle public double area(double radius) { return 3.14 * radius * radius; } // Area of a rectangle public double area(double length, double width) { return length * width; } // Area of a triangle public double area(double base, int height) { return 0.5 * base * height; Advanced Java Programming 72 } } public class Main { public static void main(String[] args) { Shape s = new Shape(); System.out.println("Area of Circle: " + s.area(5.0)); System.out.println("Area of Rectangle: " + s.area(4.0, 6.0)); System.out.println("Area of Triangle: " + s.area(8.0, 5)); } } Output: Area of Circle: 78.5 Area of Rectangle: 24.0 Area of Triangle: 20.0 1.8 Static members, Arrays and Strings Static members In Java, the keyword static is used to define members (variables, methods, blocks, or nested classes) that belong to the class itself, rather than to any specific object of that class. This means that static members are shared among all objects of the class, instead of being created separately for each object. The concept of static members helps in saving memory and allows accessing class-level data or behavior without creating an instance of the class. Static members are loaded into memory when the class is first loaded by the Java Virtual Machine (JVM), before any objects are created. Why Use Static Members? Static members are mainly used when data or behavior should be common to all instances of a class. For example, if multiple objects share the same constant value or need to perform the same utility operation, declaring those members as static prevents duplication and ensures consistency. Advanced Java Programming 73 Types of Static Members Java allows the use of the static keyword with four kinds of members: 1. Static Variables (Class Variables) 2. Static Methods 3. Static Blocks 4. Static Nested Classes 1. Static Variables A static variable (also called a class variable) is declared with the static keyword inside a class but outside any method or constructor. Unlike instance variables that belong to individual objects, a static variable is shared across all instances of a class. There is only one copy of a static variable, and it is stored in the class memory area. Example: public class Student { // static variable static String schoolName = "Sunrise Public School"; // instance variable String studentName; // constructor Student(String name) { studentName = name; } public void showDetails() { System.out.println("Student Name: " + studentName); System.out.println("School Name: " + schoolName); } public static void main(String[] args) { Student s1 = new Student("Ravi"); Student s2 = new Student("Anjali"); s1.showDetails(); s2.showDetails(); } } Advanced Java Programming 74 Output: Student Name: Ravi School Name: Sunrise Public School Student Name: Anjali School Name: Sunrise Public School Explanation: The variable schoolName is static, meaning both s1 and s2 share the same copy. If one object modifies it, the change reflects in all objects of the class. 2. Static Methods A static method is a method that belongs to the class rather than an instance. It can be called directly using the class name, without creating an object. Static methods can access only static variables and other static methods directly. They cannot access instance variables or instance methods, since they belong to the class and not to any specific object. Example: public class MathOperations { // static method public static int square(int number) { return number * number; } // non-static method public void displayMessage() { System.out.println("Non-static method called"); } public static void main(String[] args) { // calling static method directly using class name int result = MathOperations.square(5); System.out.println("Square: " + result); // To call a non-static method, we need to create an object MathOperations obj = new MathOperations(); obj.displayMessage(); } } Advanced Java Programming 75 Output: Square: 25 Non-static method called Explanation: The method square() is static, so it can be invoked without creating an object. However, the non-static method displayMessage() requires an instance to be called. 3. Static Blocks A static block in Java is used for initializing static variables or performing actions that need to be executed only once when the class is first loaded. Static blocks run before the main method and before any object of the class is created. You can have multiple static blocks in a class, and they execute in the order they appear. Example: public class StaticBlockExample { static int count; // static block static { System.out.println("Static block executed."); count = 10; } public static void main(String[] args) { System.out.println("Main method executed."); System.out.println("Count value: " + count); } } Output: Static block executed. Main method executed. Count value: 10 Explanation: The static block runs as soon as the class is loaded into memory, even before the main() method is called. It is typically used for setting up static resources. Advanced Java Programming 76 4. Static Nested Classes A static nested class is a nested (inner) class declared as static inside another class. Unlike regular inner classes, a static nested class does not require an instance of the outer class to be created. It behaves like a normal top-level class but is logically grouped inside another class for better organization. Example: public class OuterClass { private static String message = "Hello from Outer Class"; // static nested class static class NestedClass { public void display() { System.out.println(message); } } public static void main(String[] args) { // creating object of static nested class OuterClass.NestedClass nested = new OuterClass.NestedClass(); nested.display(); } } Output: Hello from Outer Class Explanation: Since NestedClass is static, it can directly access the static variable message of OuterClass. We can create its object without creating an instance of the outer class. Common Use Cases of Static Members 1. Utility or Helper Methods - Classes like Math or Collections use static methods (Math.pow(), Math.max(), etc.). 2. Constants Declaration - Use static final variables to define constants shared across all objects. 3. public static final double PI = 3.14159; 4. Counters -Use static variables to count the number of objects created. 5. static int objectCount = 0; Advanced Java Programming 83 Arrays.binarySearch(array, key) Searches for a key using the binary search algorithm (array must be sorted). Arrays.equals(array1, array2) Returns true if both arrays are equal. Arrays.fill(array, value) Assigns the same value to every element in the array. Example Using Arrays Class import java.util.Arrays; public class ArraysExample { public static void main(String[] args) { int[] numbers = {5, 2, 9, 1, 7}; // Sorting array Arrays.sort(numbers); System.out.println("Sorted Array: " + Arrays.toString(numbers)); // Searching an element int index = Arrays.binarySearch(numbers, 7); System.out.println("Index of 7: " + index); // Filling array Arrays.fill(numbers, 0); System.out.println("After Filling: " + Arrays.toString(numbers)); } } Output: Sorted Array: [1, 2, 5, 7, 9] Index of 7: 3 After Filling: [0, 0, 0, 0, 0] Explanation: Here, the Arrays class is used to sort the array, perform a binary search, and fill all elements with a single value. Advantages of Using Arrays 1. Efficient Data Storage: Arrays provide a systematic way to store multiple elements of the same data type. 2. Easy Access: Elements can be accessed directly using indices. Advanced Java Programming 84 3. Compact Memory Usage: Memory allocation is continuous and fixed in size. 4. Fast Operations: Access and modification of elements are very fast due to direct indexing. Limitations of Arrays 1. Fixed Size: Once declared, the array size cannot be changed. 2. Homogeneous Data: Arrays can only hold elements of the same data type. 3. Lack of Flexibility: Insertion and deletion operations are complex compared to collections like ArrayList. Strings A String in Java is an object that represents a sequence of characters. Although it behaves like a primitive in many ways (you can write "hello" directly), String is a class (java.lang.String) and provides many utility methods to inspect, transform, and compare sequences of characters. Strings are used everywhere user input, file contents, messages, formatting, keys in maps, etc. Creating Strings (literals and constructors) You can create strings in two common ways: string literals and constructors.  Literal: String s = "hello"; these are interned in the String Pool.  Constructor: String s2 = new String("hello"); creates a new object even if "hello" exists in the pool. Example: String a = "Java"; String b = "Java"; String c = new String("Java"); System.out.println(a == b); // true - same pooled object System.out.println(a == c); // false - different object System.out.println(a.equals(c)); // true - content equality Immutability of Strings Strings in Java are immutable: once created, their contents cannot be changed. Any operation that seems to modify a string actually creates a new String object. Immutability is important for safety (thread-safety), security (internals not tampered), and use as keys in collections. Advanced Java Programming 85 Example (immutability demonstration): String s = "cat"; s.toUpperCase(); // returned "CAT" but s still points to "cat" System.out.println(s); // prints "cat" s = s.toUpperCase(); // reassigns variable to new String "CAT" System.out.println(s); // prints "CAT" The String Pool (interning) Java keeps a special memory area called the String Pool (or intern pool). String literals are automatically placed there. Calling intern() on a String returns a pooled reference for equal content. Pooling reduces memory and speeds up equality checks via == for literals (but prefer equals() for content checks). Example: String s1 = "hello"; String s2 = new String("hello").intern(); System.out.println(s1 == s2); // true Comparing Strings (equals, ==, compareTo) Use equals() to compare contents. == checks reference identity (same object). compareTo() (from Comparable) lexicographically compares two strings and returns negative/zero/positive like most compare functions. Example: String a = "apple"; String b = "banana"; System.out.println(a.equals(b)); // false System.out.println(a.compareTo(b)); // negative (because "apple" < "banana") Commonly used String methods Java String has many useful methods. Here are the most used with short descriptions:  length() - number of characters.  charAt(int index) - character at index.  substring(int start, int end) - slice of the string (end exclusive).  indexOf(...), lastIndexOf(...) - find positions.  contains(CharSequence) - true if substring exists.  startsWith(...), endsWith(...). Advanced Java Programming 86  toLowerCase(), toUpperCase().  trim() - remove leading/trailing whitespace.  replace(oldChar, newChar) and replace(CharSequence, CharSequence).  split(regex) - break into array using regex.  equalsIgnoreCase(...) - content compare ignoring case.  format(...) - static formatting similar to String.format.  valueOf(...) - convert primitive / object to String. Examples: String s = " Hello, Java! "; System.out.println(s.length()); // 15 (counts spaces) System.out.println(s.trim()); // "Hello, Java!" System.out.println(s.substring(2, 7)); // "Hello" System.out.println(s.contains("Java")); // true System.out.println(s.replace("Java", "World")); // " Hello, World! " String[] parts = s.trim().split(",\\s*"); // ["Hello", "Java!"] String concatenation and performance Concatenation with + is easy: "a" + "b" produces "ab". The Java compiler optimizes many concatenations, but repeated concatenation in loops using + creates many temporary String objects and is inefficient. Use StringBuilder (or StringBuffer if thread-safety is required) when building strings dynamically, especially in loops. Bad (slow in loops): String s = ""; for (int i = 0; i < 1000; i++) { s += i; // creates new String each iteration } Good (fast): StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i); } String result = sb.toString(); StringBuilder vs StringBuffer  StringBuilder (since Java 5) is not synchronized -faster for single-threaded use. Advanced Java Programming 87  StringBuffer is synchronized thread-safe but slower. Prefer StringBuilder unless you need thread safety. Formatting strings (String.format) String.format uses printf-style formatting and is useful for constructing strings with numbers, padding, decimal precision, etc. Example: String name = "Priya"; int age = 25; String s = String.format("Name: %s, Age: %d", name, age); System.out.println(s); // "Name: Priya, Age: 25" double pi = Math.PI; System.out.println(String.format("Pi to 2 decimals: %.2f", pi)); // "3.14" Regular expressions with strings String and regex integrate via matches(), split(regex), and replaceAll(regex, replacement). matches() checks the whole string (not partial), so use .* if you want substring matching, or prefer Pattern/Matcher for complex needs. Example: String email = "[email protected]"; boolean ok = email.matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-z]{2,}"); System.out.println(ok); // true/false Converting to/from other types String ↔ primitives:  Integer.parseInt("123"), Double.parseDouble("3.14") - convert from String to primitive.  String.valueOf(123) or Integer.toString(123) - convert to String. Example: int n = Integer.parseInt("42"); String s = String.valueOf(3.1415); Advanced Java Programming 88 Useful idioms and tips  Always use equals() (or Objects.equals(a, b) if a may be null) to compare strings.  If you need to sort strings case-insensitively, use String::compareToIgnoreCase.  For constant concatenation ("a" + "b"), the compiler may optimize it at compile-time.  Avoid new String(...) unless you have a strong reason (it bypasses interning and wastes memory).  For heavy logging or repeated appends, prefer StringBuilder to reduce garbage. Examples 1. Check palindrome (case-insensitive, ignore spaces): public static boolean isPalindrome(String s) { String cleaned = s.replaceAll("\\s+", "").toLowerCase(); String reversed = new StringBuilder(cleaned).reverse().toString(); return cleaned.equals(reversed); } 2. Count occurrences of a substring: public static int countOccurrences(String text, String sub) { int count = 0; int idx = 0; while ((idx = text.indexOf(sub, idx)) != -1) { count++; idx += sub.length(); } return count; } 3. Build CSV row efficiently: String[] fields = {"Alice", "25", "Engineer"}; StringBuilder sb = new StringBuilder(); for (int i = 0; i < fields.length; i++) { if (i > 0) sb.append(','); sb.append(fields[i]); } String csv = sb.toString(); // "Alice,25,Engineer" Advanced Java Programming 89 When to use String vs StringBuilder Use String for static text, keys, or when you don’t mutate the content heavily. Use StringBuilder when you construct strings incrementally or inside loops. Use StringBuffer only when multiple threads will 1.9 Inheritance Inheritance in Java is a core concept of Object-Oriented Programming (OOP). It allows one class to acquire the properties (fields) and behaviors (methods) of another class. In simple terms, inheritance enables the creation of new classes derived from existing ones, allowing code reuse and improved organization. A subclass that inherits from another class can utilize the fields and methods defined in its parent class. Example: In the example below, Animal serves as the base (parent) class, while Dog, Cat, and Cow are subclasses that extend the Animal class. Implementation: // Parent class class Animal { void sound() { System.out.println("Animal makes a sound"); } } // Child class class Dog extends Animal { void sound() { System.out.println("Dog barks"); } } Advanced Java Programming 90 // Child class class Cat extends Animal { void sound() { System.out.println("Cat meows"); } } // Child class class Cow extends Animal { void sound() { System.out.println("Cow moos"); } } // Main class public class Geeks { public static void main(String[] args) { Animal a; a = new Dog(); a.sound(); a = new Cat(); a.sound(); a = new Cow(); a.sound(); } Advanced Java Programming 91 } Output Dog barks Cat meows Cow moos Explanation:  Animal is the base class.  Dog, Cat and Cow are derived classes that extend Animal class and provide specific implementations of the sound() method.  The Geeks class is the driver class that creates objects and demonstrates runtime polymorphism using method overriding. Syntax class ChildClass extends ParentClass { // Additional fields and methods } Why We use Inheritance in Java? Code Reusability Inheritance promotes code reusability, as the code written in a superclass can be shared among all its subclasses. This allows child classes to directly access and use the functionality defined in the parent class, reducing redundancy. Method Overriding Inheritance makes method overriding possible, which is essential for achieving Run-Time Polymorphism in Java. Through overriding, a subclass can provide its own implementation of a method that already exists in the superclass. Abstraction Inheritance also supports abstraction, allowing developers to hide unnecessary details and show only the essential functionality to the user. This helps simplify complex systems by focusing on what an object does rather than how it does it. Advanced Java Programming 92 Key Terminologies in Java Inheritance Class A class is a blueprint or template used to create objects that share similar attributes and behaviors. It is not a real-world entity but a structural representation that defines the properties and methods common to a group of objects. Super Class / Parent Class The superclass (also called the base or parent class) is the class whose properties and methods are inherited by another class. Sub Class / Child Class The subclass (also called the derived, extended, or child class) is the class that inherits from another class. It can use the parent class’s members and also define its own additional fields and methods. Extends Keyword The extends keyword is used in Java to enable one class to inherit from another class. How Inheritance Works in Java In Java, inheritance is implemented using the extends keyword. When a class extends another, it automatically acquires all the non-private members (fields and methods) of the parent class. The subclass can then use these inherited members, override existing methods, or introduce new ones to extend or modify the parent class’s functionality. Inheritance is one of the core principles of Object-Oriented Programming (OOP) in Java. It allows one class to acquire the properties and behaviors (fields and methods) of another class. Depending on how classes inherit from one another, Java supports several types of inheritance: 1. Single Inheritance 2. Multilevel Inheritance 3. Hierarchical Inheritance 4. Multiple Inheritance (through Interfaces) 5. Hybrid Inheritance (through Interfaces) Advanced Java Programming 99 true In the above example:  Vehicle is the superclass of Car and Bike.  Car and Bike are subclasses of Vehicle.  ElectricCar is a subclass of both Car and Vehicle. What Can Be Done in a Subclass? In a subclass, you can perform several actions involving the inherited members: 1. Use inherited fields and methods directly as if they were defined in the subclass. 2. Declare new fields specific to the subclass. 3. Override methods from the superclass to provide a new implementation. 4. Hide static methods in the subclass using the same method name. 5. Add new methods that don’t exist in the parent class. 6. Invoke the superclass constructor using the super keyword. Example: class Employee { String name = "Ravi"; void work() { System.out.println(name + " works 8 hours a day"); } } class Manager extends Employee { String department = "Sales"; @Override void work() { System.out.println(name + " manages the " + department + " department"); } void showDetails() { super.work(); // calling parent method work(); // calling overridden method } } public class SubclassDemo { public static void main(String[] args) { Manager m = new Manager(); Advanced Java Programming 100 m.showDetails(); } } Output: Ravi works 8 hours a day Ravi manages the Sales department Advantages of Inheritance in Java 1. Code Reusability: Inheritance allows you to reuse existing code. Subclasses can utilize fields and methods from their parent classes, reducing duplication. 2. Abstraction: It supports abstraction by allowing developers to define generalized behavior in abstract classes and implement specific functionality in subclasses. 3. Class Hierarchy: Inheritance helps organize code into a structured class hierarchy, reflecting real-world relationships between objects. 4. Polymorphism: Through inheritance, Java enables polymorphism, allowing objects to take multiple forms and execute overridden methods dynamically at runtime. Disadvantages of Inheritance in Java 1. Increased Complexity: Deep or poorly designed inheritance hierarchies can make code more complicated and harder to debug. 2. Tight Coupling: Subclasses are tightly dependent on their parent classes. Any modification in the superclass can unintentionally affect the behavior of the subclasses. Constructors in inheritance In Java, when one class inherits another, both the parent and child classes can define their own constructors. However, the constructor of the parent class is always executed first, followed by the constructor of the child class. This ensures that the base (parent) class is properly initialized before the subclass adds its own additional functionality. How Constructors Work in Inheritance When an object of a subclass is created, Java automatically calls the constructor of its superclass before executing the subclass constructor. This happens so that the inherited properties of the parent class are set up first. If the subclass does not explicitly call a parent constructor using super(), the Java compiler automatically inserts a call to the default constructor of the parent class (if it exists). Advanced Java Programming 101 If the parent class has no default constructor, you must explicitly call one of its parameterized constructors using super(arguments); otherwise, the code will fail to compile. Example 1: Default Constructor in Inheritance When a subclass object is created, the default constructor of the parent class executes automatically before the child constructor. class Animal { Animal() { System.out.println("Parent class constructor: Animal"); } } class Dog extends Animal { Dog() { System.out.println("Child class constructor: Dog"); } } public class Demo1 { public static void main(String[] args) { Dog obj = new Dog(); } } Output Parent class constructor: Animal Child class constructor: Dog Explanation When the Dog object is created: 1. Java automatically calls the parent (Animal) constructor first. 2. Once the parent class is initialized, the Dog constructor executes. 3. This ensures proper initialization from top to bottom in the inheritance chain. Example 2: Using super() to Call a Parameterized Constructor By default, Java calls the no-argument constructor of the superclass. However, if the superclass defines only a parameterized constructor, you must explicitly call it using the super() keyword from the subclass constructor. Advanced Java Programming 102 class Employee { Employee(String name) { System.out.println("Employee constructor called. Name: " + name); } } class Manager extends Employee { Manager(String name, String department) { super(name); // Explicitly calling parent class constructor System.out.println("Manager constructor called. Department: " + department); } } public class Demo2 { public static void main(String[] args) { Manager obj = new Manager("Alice", "Finance"); } } Output Employee constructor called. Name: Alice Manager constructor called. Department: Finance Explanation  The super(name) statement explicitly calls the parent class (Employee) constructor with a parameter.  This ensures that the Employee part of the object is properly initialized before executing the Manager constructor. Constructor Chaining in Inheritance Constructor chaining refers to the process where one constructor calls another constructor either within the same class (using this()) or from its parent class (using super()). This chaining continues until the topmost constructor (of the base class) is executed. Example 3: Constructor Chaining Across Multiple Classes class Device { Device() { System.out.println("Constructor of Device"); } } Advanced Java Programming 103 class Mobile extends Device { Mobile() { super(); // Calls Device constructor System.out.println("Constructor of Mobile"); } } class Smartphone extends Mobile { Smartphone() { super(); // Calls Mobile constructor System.out.println("Constructor of Smartphone"); } } public class Demo3 { public static void main(String[] args) { Smartphone obj = new Smartphone(); } } Output Constructor of Device Constructor of Mobile Constructor of Smartphone Explanation  When the Smartphone object is created, the constructor of Device runs first, followed by Mobile, and finally Smartphone.  The super() calls ensure constructors execute from top to bottom in the inheritance hierarchy. Example 4: Constructor Chaining with this() and super() In Java, this() is used to call another constructor in the same class, while super() calls the constructor of the superclass. Both cannot be used together in the same constructor, as each must be the first statement in the constructor body. class Vehicle { Vehicle(String type) { System.out.println("Vehicle Type: " + type); } } Advanced Java Programming 104 class Car extends Vehicle { Car() { this("Sedan"); // Calls another constructor in the same class System.out.println("Default Car constructor"); } Car(String model) { super("Car"); // Calls parent constructor System.out.println("Car model: " + model); } } public class Demo4 { public static void main(String[] args) { Car obj = new Car(); } } Output Vehicle Type: Car Car model: Sedan Default Car constructor Explanation 1. The default Car() constructor first calls another constructor of the same class using this("Sedan"). 2. That constructor (Car(String model)) then calls the parent (Vehicle) constructor using super("Car"). 3. Execution flows upward to the base class first, then moves down the chain. Example 5: Constructor Execution Order in Multi-Level Inheritance class University { University() { System.out.println("Constructor of University"); } } class Department extends University { Department() { System.out.println("Constructor of Department"); } } Advanced Java Programming 105 class Student extends Department { Student() { System.out.println("Constructor of Student"); } } public class Demo5 { public static void main(String[] args) { Student s = new Student(); } } Output Constructor of University Constructor of Department Constructor of Student Explanation Constructors are executed in hierarchical order: 1. University (base class) 2. Department (intermediate class) 3. Student (derived class) This ensures that initialization happens step-by-step starting from the most general class to the most specific. Key Points  The constructor of the parent class always executes before the child class constructor.  If there is no explicit super() call, Java automatically calls the parent’s default constructor.  If the parent class doesn’t have a default constructor, you must call one of its constructors using super(arguments).  super() and this() cannot appear in the same constructor because both must be the first statement.  Constructor chaining allows multiple constructors to execute in an ordered manner from the parent class to the child class. Method Overriding Method overriding is a core OOP feature that lets a subclass provide a specific implementation for a method that is already defined in its superclass. Overriding is the Advanced Java Programming 106 mechanism Java uses to support runtime polymorphism: a reference of the parent type can refer to an object of the child type, and the child’s overridden method will run. Method overriding occurs when a subclass defines a method with the same name, same parameter list (signature) and compatible return type as a method in its superclass. The subclass’s method replaces (overrides) the superclass method for instances of the subclass. This lets subclasses modify or extend behaviour defined in the parent class. Why override methods? Overriding is used to:  Customize or extend behavior for subclasses without changing the superclass.  Implement polymorphic behavior so that code can work with parent-type references but execute child-specific implementations at runtime.  Provide specific implementations for abstract methods declared in abstract classes or interfaces. Rules for overriding  Same method signature: name and parameter types must match exactly.  Return type: must be the same or a covariant return type (a subtype of the original return type).  Access level: subclass method cannot have a more restrictive access modifier than the superclass method (e.g., a public method cannot be overridden as protected).  Exceptions: an overriding method cannot throw broader checked exceptions than the overridden method; it may throw fewer or narrower checked exceptions, or any unchecked exceptions.  final methods: cannot be overridden.  static methods: are not overridden they are hidden. The compile-time type determines which static method is called.  Constructors: cannot be overridden.  Use @Override annotation (recommended) it helps the compiler catch signature mistakes. Basic example: simple override class Animal { void sound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override Advanced Java Programming 107 void sound() { System.out.println("Dog barks"); } } public class Demo { public static void main(String[] args) { Animal a = new Dog(); // parent reference, child object a.sound(); // prints "Dog barks" child method executed } } Explanation: Although the reference type is Animal, the JVM dispatches to Dog.sound() at runtime because the actual object is a Dog. Overriding and runtime polymorphism Method overriding enables writing general code that works for many types: class Shape { void draw() { System.out.println("Drawing shape"); } } class Circle extends Shape { @Override void draw() { System.out.println("Drawing circle"); } } class Square extends Shape { @Override void draw() { System.out.println("Drawing square"); } } public class Renderer { static void render(Shape s) { s.draw(); // runtime chooses correct draw() } public static void main(String[] args) { render(new Circle()); // "Drawing circle" render(new Square()); // "Drawing square" } } You can add new subclasses without changing render(); polymorphism does the dispatching. Advanced Java Programming 108 Covariant return types Java allows a subclass overriding method to return a subtype of the original return type: class Parent { Number getValue() { return 42; } } class Child extends Parent { @Override Integer getValue() { return 42; } // Integer is subtype of Number } This is legal and often useful for more specific return values in subclasses. Access modifiers and overriding You cannot reduce visibility when overriding: class A { protected void show() { } } class B extends A { // public is allowed (wider), protected is allowed (same), private is not allowed (narrower) @Override public void show() { } // OK } If you try to change protected to private, the compiler will error. Exceptions in overriding An overriding method cannot declare new or broader checked exceptions than the overridden method: class X { void process() throws java.io.IOException { } } class Y extends X { @Override void process() throws java.io.FileNotFoundException { } // OK (narrower) // void process() throws Exception { } // NOT allowed broader checked exception } Advanced Java Programming 115 Shape provides shared code (color field and display() method). Circle and Rectangle must implement area(). Combining super and abstract classes (common pattern) It’s common for an abstract class to define constructor logic or helper methods that subclasses use through super(...). The abstract base initializes shared state; each concrete subclass calls super(...) to ensure that shared initialization happens. Example: abstract base with constructor + subclass using super abstract class Vehicle { String brand; Vehicle(String brand) { this.brand = brand; } abstract void start(); void showBrand() { System.out.println("Brand: " + brand); } } class Bike extends Vehicle { Bike(String brand) { super(brand); // initialize Vehicle } @Override void start() { System.out.println("Kick start the bike"); } } public class CombinedDemo { public static void main(String[] args) { Vehicle v = new Bike("Yamaha"); v.showBrand(); // uses concrete method from abstract class v.start(); // subclass implementation } } Advanced Java Programming 116 Useful patterns and design idioms Template Method Pattern: An abstract class defines a template method (concrete) that calls several abstract steps. Subclasses provide those steps. This is a textbook pattern that leverages both abstract methods and concrete helpers in the base class. Partial implementation: Use abstract classes when you want to force certain implementations but provide shared utilities (e.g., protected helper methods or cached fields). Constructor initialization: Use super(...) in subclass constructors to ensure base state is set consistently. Template Method quick example abstract class DataProcessor { // template method (final so subclasses can't change the sequence) public final void process() { read(); transform(); write(); } abstract void read(); abstract void transform(); void write() { System.out.println("Default write to console"); } } class CSVProcessor extends DataProcessor { void read() { System.out.println("Read CSV"); } void transform() { System.out.println("Transform CSV rows"); } } 1.10 Interfaces An interface in Java is a reference type that declares a contract: a set of methods (and constants) that a class can implement. Interfaces describe what an implementing class must do, not how it does it. They are central to Java’s type system and are the primary tool for achieving multiple-inheritance-like design, loose coupling, and polymorphism. An interface is a collection of abstract method signatures and constants. A class that implements an interface agrees to provide concrete implementations of its abstract methods. Advanced Java Programming 117 Interfaces are a way to specify capabilities (e.g., Comparable, Runnable) without prescribing a class hierarchy. Key implicit modifiers  Methods declared in an interface are implicitly public (and prior to Java 8: abstract).  Fields declared in an interface are implicitly public static final (constants). Why use interfaces? Multiple-type inheritance: A class can implement many interfaces, allowing it to present multiple capabilities without the complications of multiple class inheritance. Decoupling and flexibility: Code can depend on interface types, not concrete classes, which makes testing and swapping implementations easier. Polymorphism: You can write methods that accept interface types and work with any implementing class. API contracts: Interfaces define clear contracts for library consumers. Basic example: defining and implementing an interface interface Drivable { void accelerate(int increment); void brake(int decrement); } class Car implements Drivable { private int speed = 0; @Override public void accelerate(int increment) { speed += increment; System.out.println("Car speed: " + speed); } Advanced Java Programming 118 @Override public void brake(int decrement) { speed = Math.max(0, speed - decrement); System.out.println("Car speed: " + speed); } } Here, Car implements Drivable and must provide both methods. Default and static methods To evolve interfaces without breaking existing implementations, Java 8 introduced default methods (concrete methods with default keyword) and static methods inside interfaces. default methods let interfaces provide a default implementation that implementing classes may override or reuse. static methods are utility methods related to the interface and are called on the interface itself. interface Logger { default void log(String msg) { System.out.println("[LOG] " + msg); } static void info(String msg) { System.out.println("[INFO] " + msg); } } class App implements Logger { // inherits default log(), can override if needed } Advanced Java Programming 119 Use: Logger.info("starting") or new App().log("hello"). Private methods in interfaces Interfaces may contain private methods to reuse code between multiple default methods. These are only callable from inside interface methods. interface Formatter { default void print(String s) { System.out.println(format(s)); } private String format(String s) { return "[" + s.trim() + "]"; } } Multiple inheritance via interfaces and conflict resolution A class can implement multiple interfaces. If two interfaces provide default methods with the same signature, the implementing class must resolve the conflict either override the method or specify which super-interface’s default to use via InterfaceName.super.method(). interface A { default void hello() { System.out.println("A"); } } interface B { default void hello() { System.out.println("B"); } } class MyClass implements A, B { @Override public void hello() { A.super.hello(); // choose A's default // or B.super.hello(); } Advanced Java Programming 120 } This explicit resolution prevents ambiguity (the diamond problem). Functional interfaces and lambdas A functional interface is an interface with exactly one abstract method (it may have default/static methods as well). They are targets for lambda expressions and method references. Common examples: Runnable, Callable<V>, Comparator<T>, Function<T,R>. @FunctionalInterface interface Transformer { String transform(String input); } public class LambdaDemo { public static void main(String[] args) { Transformer toUpper = s -> s.toUpperCase(); System.out.println(toUpper.transform("hello")); // HELLO } } Using @FunctionalInterface is optional but documents intent and enables compile-time checks. Marker interfaces A marker interface contains no methods; it signals metadata about a class to the runtime or frameworks (e.g., Serializable, Cloneable). Marker interfaces are an older pattern; annotations are often used instead today. class MyData implements java.io.Serializable { /* no methods to implement */ } Interface inheritance (interfaces extending interfaces) Advanced Java Programming 121 An interface can extend one or more other interfaces, inheriting their abstract and default methods. This allows composing contracts. interface Readable { void read(); } interface Writable { void write(); } interface ReadWritable extends Readable, Writable { /* combines both */ } Interfaces vs Abstract Classes - when to use which? Use an interface when you want to define a capability or contract that many unrelated classes might implement (e.g., Comparable, Iterable), or when you need multiple inheritance of type. Use an abstract class when you want to share state (fields) or partial implementation among closely related classes. An abstract class can have instance fields; interfaces cannot (only constants). Since Java 8/9 interfaces can have default/static/private methods, the line has blurred but interfaces still cannot hold instance state. Practical examples 1) Multiple capability example interface Flyable { void fly(); } interface Swimmable { void swim(); } class Duck implements Flyable, Swimmable { public void fly() { System.out.println("Duck flies"); } public void swim() { System.out.println("Duck swims"); } } Duck can be used anywhere a Flyable or Swimmable is needed. 2) Default method override and reuse interface Validator { default boolean isValid(String s) { return s != null && !s.isBlank(); } Advanced Java Programming 122 } class NameValidator implements Validator { @Override public boolean isValid(String s) { return Validator.super.isValid(s) && s.length() <= 50; } } NameValidator reuses the interface’s default check and adds extra rules. 3) Functional interface with a lambda import java.util.function.Function; public class FuncExample { public static void main(String[] args) { Function<Integer, String> toHex = i -> Integer.toHexString(i); System.out.println(toHex.apply(255)); // ff } } Dynamic method dispatch Dynamic Method Dispatch is a core concept in Object-Oriented Programming (OOP) and one of the most powerful features of Java’s runtime polymorphism. It refers to the process of resolving which version of an overridden method to call at runtime, rather than at compile time. This mechanism allows Java to support polymorphic behavior, where the method that gets executed depends on the actual object type being referenced, not the reference variable type. Dynamic Method Dispatch, also known as Runtime Polymorphism, occurs when a superclass reference variable is used to refer to an object of a subclass. When an overridden method is called through the superclass reference, Java determines which version of the Advanced Java Programming 123 method to execute based on the object that the reference variable points to not on the type of the reference itself. The method call is resolved at runtime, depending on the actual object being referred to. This contrasts with compile-time polymorphism, such as method overloading, where the decision about which method to call is made during compilation. How Dynamic Method Dispatch Works 1. A superclass reference variable can hold a reference to any of its subclass objects. 2. When an overridden method is invoked on this reference, the JVM determines which version of the method to call based on the actual object (subclass) being referred to at runtime. 3. This enables Java to exhibit different behaviors depending on the object type, even when using a single reference variable. Example: Demonstrating Dynamic Method Dispatch class Animal { void sound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override void sound() { System.out.println("Dog barks"); } } class Cat extends Animal { @Override void sound() { System.out.println("Cat meows"); } } public class DynamicDispatchExample { public static void main(String[] args) { Animal a; // reference of superclass a = new Dog(); // refers to Dog object a.sound(); // calls Dog's version of sound() Advanced Java Programming 124 a = new Cat(); // refers to Cat object a.sound(); // calls Cat's version of sound() } } Output Dog barks Cat meows Explanation  The reference variable a is of type Animal (the parent class).  Initially, it points to a Dog object, so Dog’s version of sound() executes.  Later, the same reference a points to a Cat object, so Cat’s version of sound() executes. Here, the method call a.sound() is resolved at runtime, depending on the actual object (Dog or Cat) this is what makes it dynamic. Importance of Dynamic Method Dispatch 1. Achieves Runtime Polymorphism: It allows one interface (like a parent class reference) to be used for multiple actual types (child class objects). 2. Flexibility and Extensibility: It enables programs to be more flexible and easily extendable. New subclasses can be added without changing the existing code that uses the superclass reference. 3. Code Reusability: It allows developers to write code that can operate on objects of different classes through a common interface or parent class. Packages Packages in Java are one of the most important mechanisms for organizing and managing large programs. They serve as containers for classes, interfaces, enumerations, and annotations, allowing developers to group related types together in a single namespace. Packages help in avoiding naming conflicts, controlling access, and making code modular and easier to maintain. In short, packages in Java are similar to folders in a file system they provide a structured way to organize your source files and compiled class files. A Java package is a namespace that groups related classes and interfaces together. Packages help in logically categorizing Java classes based on their functionality so that classes, interfaces, and other components can be easily located and reused. In other words, a package acts as a container that provides access protection and namespace management. It ensures that two classes with the same name can exist in different packages Advanced Java Programming 131 powerful mechanism called exception handling that allows developers to detect, handle, and recover from runtime errors gracefully. An exception is an event that disrupts the normal flow of a program’s execution. It occurs during the execution of a program and indicates that something unexpected has happened, such as dividing by zero, accessing an invalid array index, or attempting to open a file that doesn’t exist. In Java, exceptions are objects derived from the class java.lang.Throwable. When an exception occurs, the Java runtime system creates an exception object and hands it over to the runtime environment to handle the situation. If the exception is not properly handled, the program terminates abruptly, displaying an error message and a stack trace. Types of Exceptions Java classifies exceptions into three main categories: 1. Checked Exceptions Checked exceptions are those that the compiler checks at compile-time. These exceptions must be either caught using a try-catch block or declared in the method signature using the throws keyword. They usually occur due to external factors like file handling, network connections, or input/output operations. Example: IOException, SQLException, FileNotFoundException import java.io.*; public class CheckedExample { public static void main(String[] args) { try { FileReader fr = new FileReader("data.txt"); } catch (FileNotFoundException e) { System.out.println("File not found: " + e); } } } Explanation: The compiler forces you to handle FileNotFoundException, because file operations depend on external resources and may fail. Advanced Java Programming 132 2. Unchecked Exceptions Unchecked exceptions (also called runtime exceptions) are not checked at compile time. These exceptions usually occur due to programming logic errors and can be avoided through better coding practices. If an unchecked exception occurs and is not handled, the program terminates abnormally. Example: ArithmeticException, ArrayIndexOutOfBoundsException, NullPointerException public class UncheckedExample { public static void main(String[] args) { int[] arr = {1, 2, 3}; try { System.out.println(arr[5]); // invalid index } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Exception caught: " + e); } System.out.println("Program continues..."); } } Output: Exception caught: java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3 Program continues... 3. Errors Errors are serious issues that occur beyond the control of the programmer. They are not meant to be handled by programs, as they represent system-level problems like hardware failure, JVM crashes, or memory exhaustion. Example: OutOfMemoryError, StackOverflowError public class ErrorExample { public static void main(String[] args) { try { ErrorExample.recursive(); // infinite recursion } catch (StackOverflowError e) { System.out.println("Stack overflow occurred!"); } } static void recursive() { Advanced Java Programming 133 recursive(); // causes StackOverflowError } } Exception Hierarchy All exceptions and errors in Java are derived from the class Throwable, which has two main subclasses: Throwable │ ├── Exception │ ├── IOException │ ├── SQLException │ ├── RuntimeException │ ├── ArithmeticException │ ├── NullPointerException │ └── IndexOutOfBoundsException │ └── Error ├── OutOfMemoryError ├── StackOverflowError └── VirtualMachineError Java Exception Handling Mechanism Java uses five keywords for exception handling: 1. try – Block of code that may throw an exception. 2. catch – Used to handle the exception that occurs in the try block. 3. finally – Used to execute important code such as resource cleanup, regardless of whether an exception occurs. 4. throw – Used to explicitly throw an exception. 5. throws – Declares exceptions that can be thrown by a method. Basic Syntax of Exception Handling try { // risky code that may throw exception } catch (ExceptionType e) { // handle the exception } finally { // optional block, always executed } Advanced Java Programming 134 Example: Basic Try-Catch Block public class DivisionExample { public static void main(String[] args) { int a = 10, b = 0; try { int result = a / b; // division by zero System.out.println("Result: " + result); } catch (ArithmeticException e) { System.out.println("Error: Cannot divide by zero!"); } System.out.println("Program continues..."); } } Output: Error: Cannot divide by zero! Program continues... Multiple Catch Blocks You can have multiple catch blocks to handle different types of exceptions thrown in the same try block. Each block catches a specific type of exception. public class MultipleCatchExample { public static void main(String[] args) { try { int[] nums = {1, 2, 3}; System.out.println(nums[5]); } catch (ArithmeticException e) { System.out.println("Arithmetic error occurred!"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Array index is invalid!"); } catch (Exception e) { System.out.println("General exception occurred!"); } } } Output: Array index is invalid! Advanced Java Programming 135 Nested Try-Catch Blocks A try block can be nested inside another try block. This is useful when different sections of code can throw different exceptions. public class NestedTryExample { public static void main(String[] args) { try { try { int[] arr = new int[3]; arr[5] = 10; // inner try block } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Inner catch: Invalid index!"); } int x = 10 / 0; // outer try block } catch (ArithmeticException e) { System.out.println("Outer catch: Division by zero!"); } } } Output: Inner catch: Invalid index! Outer catch: Division by zero! The finally Block The finally block always executes, regardless of whether an exception occurs or not. It is typically used for resource cleanup operations, such as closing files or database connections. public class FinallyExample { public static void main(String[] args) { try { int num = 5 / 0; } catch (ArithmeticException e) { System.out.println("Exception caught: " + e); } finally { System.out.println("This block always executes."); } } } Advanced Java Programming 136 Output: Exception caught: java.lang.ArithmeticException: / by zero This block always executes. Throwing Exceptions Using throw The throw keyword is used to explicitly throw an exception from a method or any block of code. It is followed by an instance of Throwable or its subclasses. public class ThrowExample { static void checkAge(int age) { if (age < 18) throw new ArithmeticException("Access denied - You must be 18 or older."); else System.out.println("Access granted - You are old enough!"); } public static void main(String[] args) { try { checkAge(16); } catch (ArithmeticException e) { System.out.println(e.getMessage()); } } } Output: Access denied - You must be 18 or older. Declaring Exceptions Using throws The throws keyword is used in method declarations to specify the types of exceptions that the method may throw. This alerts the caller that it must handle or declare the exception. import java.io.*; public class ThrowsExample { static void readFile() throws IOException { FileReader fr = new FileReader("data.txt"); fr.read(); fr.close(); } public static void main(String[] args) { Advanced Java Programming 137 try { readFile(); } catch (IOException e) { System.out.println("Exception handled: " + e); } } } Custom (User-Defined) Exceptions Java allows you to create your own exceptions by extending the Exception class. These are useful when you want to handle application-specific error conditions. class InvalidAgeException extends Exception { InvalidAgeException(String message) { super(message); } } public class CustomExceptionExample { static void validateAge(int age) throws InvalidAgeException { if (age < 18) throw new InvalidAgeException("Age is below 18 - Not eligible to vote."); else System.out.println("Eligible to vote."); } public static void main(String[] args) { try { validateAge(15); } catch (InvalidAgeException e) { System.out.println("Caught exception: " + e.getMessage()); } } } Output: Caught exception: Age is below 18 - Not eligible to vote. Advantages of Exception Handling 1. Improves Program Reliability: Prevents abrupt program termination. 2. Separates Error-Handling Code: Keeps the main logic separate from error-handling logic. Advanced Java Programming 138 3. Propagates Errors: Allows exceptions to be passed up the call stack. 4. Group Error Types: Enables specific handling for different types of exceptions. 5. Ensures Resource Cleanup: finally ensures resources are released even when exceptions occur. Practical 1. Write a Java program that demonstrates the use of classes, objects, constructors, method overloading, inheritance, super, and abstract classes. Aim To Write a Java program that demonstrates the use of classes, objects, constructors, constructor chaining, method overloading, inheritance, super, method overriding, abstract classes, and polymorphism. Procedure 1. Define an abstract base class Person with fields, a constructor, and an abstract method showDetails(). 2. Create a concrete class Employee that extends Person. Implement showDetails(), provide overloaded methods calculateSalary(...) (demonstrating method overloading), and show a constructor that calls super(...). 3. Create a subclass Manager that extends Employee and overrides showDetails(); demonstrate super.method() usage inside the overridden method and constructor chaining. 4. In the main method of a public driver class InheritanceDemo, create objects of Employee and Manager, call overloaded methods and overridden methods, and display constructor invocation order and runtime polymorphism. 5. Compile and run to observe output demonstrating the required concepts. Program // InheritanceDemo.java // Demonstrates: classes, objects, constructors, constructor chaining, method overloading, // inheritance, use of super, abstract classes, method overriding and polymorphism. abstract class Person { protected String name; protected int age; Advanced Java Programming 139 // Constructor for Person Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person constructor called for: " + name); } // Abstract method - must be implemented by subclasses public abstract void showDetails(); // Concrete helper method public void basicInfo() { System.out.println("Basic Info -> Name: " + name + ", Age: " + age); } } class Employee extends Person { protected String employeeId; protected double baseSalary; // Constructor chaining: calls Person constructor using super(...) Employee(String name, int age, String employeeId, double baseSalary) { super(name, age); // calls Person constructor this.employeeId = employeeId; this.baseSalary = baseSalary; System.out.println("Employee constructor called for ID: " + employeeId); } // Overloaded constructors (constructor overloading) Employee(String name, int age, String employeeId) { this(name, age, employeeId, 25000.0); // calls the other constructor in same class } // Implement abstract method from Person @Override public void showDetails() { // reuse parent helper method basicInfo(); System.out.println("Role: Employee, Employee ID: " + employeeId); } // Method overloading: calculateSalary with different parameter lists // 1) No-arg: returns base salary public double calculateSalary() { Advanced Java Programming 140 System.out.println("calculateSalary() called"); return baseSalary; } // 2) With bonus public double calculateSalary(double bonus) { System.out.println("calculateSalary(double bonus) called"); return baseSalary + bonus; } // 3) With bonus and allowance public double calculateSalary(double bonus, double allowance) { System.out.println("calculateSalary(double bonus, double allowance) called"); return baseSalary + bonus + allowance; } // 4) Using number of days worked to pro-rate salary (different signature) public double calculateSalary(int daysWorked, int totalWorkingDays) { System.out.println("calculateSalary(int daysWorked, int totalWorkingDays) called"); if (totalWorkingDays == 0) return 0; return (baseSalary * daysWorked) / totalWorkingDays; } // A method that can be overridden public void work() { System.out.println(name + " (Employee) is doing general work."); } } class Manager extends Employee { private String department; // Manager constructor calls Employee constructor using super(...) Manager(String name, int age, String employeeId, double baseSalary, String department) { super(name, age, employeeId, baseSalary); // Person <- Employee <- Manager this.department = department; System.out.println("Manager constructor called for department: " + department); } // Overloaded Manager constructor (demonstrates constructor chaining using this()) Manager(String name, int age, String employeeId, String department) { this(name, age, employeeId, 50000.0, department); // default baseSalary } Advanced Java Programming 243 1. Client Layer  This is the front-end layer where users interact with the application.  It can be accessed through web browsers, mobile apps, or desktop software.  Common technologies include HTML, CSS, JavaScript, and JSP (JavaServer Pages). 2. Web Layer (Servlet Layer)  The web layer handles user requests and responses.  It uses Servlets, JSP, and JSF to process incoming data from clients.  It then forwards the processed data to the business layer for further actions. 3. Business Layer (Logic Layer)  This layer contains the core business logic of the application.  It utilizes Enterprise JavaBeans (EJB) to manage tasks such as transactions, calculations, and security.  The business layer processes data and interacts with the data layer for information retrieval and storage. 4. Data Layer  Responsible for storing and retrieving data from databases.  Uses JPA (Java Persistence API) and JDBC for database operations.  Ensures secure and efficient data handling and persistence. 5. Integration Layer (Optional)  Used for connecting applications to external systems, APIs, and services.  Implements technologies like JMS (Java Messaging Service) and web services to enable system communication. Core Components of Java EE Java EE consists of various components that simplify the creation of scalable, secure, and high-performance enterprise applications. 1. Servlets Servlets are server-side Java programs used to handle client requests and generate dynamic responses. They extend the functionality of web servers and are essential for creating webbased applications. Advanced Java Programming 244 2. JSP (JavaServer Pages) JSP allows embedding Java code within HTML pages to create dynamic and interactive web content. It helps separate the presentation layer from the business logic, making maintenance easier. JSP also supports features like Expression Language and Custom Tags. 3. JSF (JavaServer Faces) JSF is a framework for developing web-based user interfaces. It provides pre-built UI components such as buttons, forms, and tables, making development faster. It integrates with managed beans to handle user input and actions efficiently. 4. EJB (Enterprise JavaBeans) EJB is used to handle complex business logic, transactions, and security. It allows developers to build reusable, scalable, and secure components for enterprise applications— commonly used in sectors like banking and e-commerce. 5. CDI (Contexts and Dependency Injection) CDI simplifies dependency management and object lifecycle handling. It makes applications more modular, flexible, and maintainable by managing relationships between different application components automatically. 6. JAAS (Java Authentication and Authorization Service) JAAS provides authentication and access control mechanisms. It ensures that only authorized users can access specific parts of the system by verifying user credentials through usernames, passwords, or other methods. 7. JDBC (Java Database Connectivity) JDBC is a Java API that allows applications to connect to databases, execute SQL queries, and retrieve data. It enables Java programs to interact with popular databases such as MySQL, Oracle, and SQL Server. 8. Managed Beans Managed Beans are simple Java classes automatically created and managed by the Java EE container. They are primarily used in JSF and CDI for handling business logic and user interactions in web applications. Advanced Java Programming 245 9. Bean Validation Bean Validation ensures that user input and data conform to specific rules or constraints. Using annotations such as @NotNull, @Size, and @Pattern, it helps maintain data accuracy and consistency. 10. Application Client An Application Client is a standalone Java program that runs on a user’s computer and communicates with a Java EE server to access enterprise services such as EJB, JPA, and JMS. It operates independently from the main web application but can exchange data with it. Difference Between JEE and JSE Features Java EE (Enterprise Edition) Java SE (Standard Edition) Full Form Java Enterprise Edition Java Standard Edition Purpose For developing large-scale enterprise and web applications For general-purpose programming and desktop applications Components Includes APIs like Servlets, JSP, EJB, JPA, JMS, CDI Includes core Java libraries such as Collections, Threads, I/O Web Support Built-in support for web development No built-in web support Examples Web applications, banking systems, e-commerce platforms Desktop or scientific applications Table. 3.1 JEE Vs JSE. Applications of Java EE 1. Web Applications Java EE is commonly used for building dynamic websites, portals, and dashboards. Example: E-commerce websites like Amazon use technologies such as Servlets, JSP, and JSF. 2. Enterprise Applications Used for developing large-scale business applications to manage enterprise operations. Advanced Java Programming 246 Example: ERP (Enterprise Resource Planning) and CRM (Customer Relationship Management) systems. 3. Banking and Financial Applications Java EE provides the security and reliability needed for financial systems. Examples: Net banking portals, online trading systems, and credit card processing platforms like Visa and Mastercard. 4. Cloud-Based Applications Java EE supports microservices and cloud deployments using tools like Docker. Example: SaaS (Software as a Service) platforms such as Salesforce. 5. Healthcare Systems Java EE is used in hospital management systems, electronic medical records, and telemedicine solutions. Example: Health information management systems used by hospitals. 3.2 Application Servers and Containers Application Servers An Application Server is a central component in the Java Enterprise Edition (Java EE) ecosystem (now known as Jakarta EE) that provides a complete environment for developing, deploying, and managing large-scale enterprise applications. It acts as a middleware layer between the client interface (such as a web browser or mobile app) and the backend systems like databases or external APIs. In simpler terms, an application server is like the “brain” of an enterprise application it runs the business logic, manages user requests, handles transactions, and ensures the application operates securely and efficiently. Role of an Application Server The primary role of an application server is to host and execute enterprise applications built using Java EE technologies such as Servlets, JavaServer Pages (JSP), Enterprise JavaBeans (EJB), and Java Persistence API (JPA). These technologies together enable dynamic web content, database interaction, and distributed computing. The application server ensures that all these components work together seamlessly. It manages requests from clients, connects Advanced Java Programming 247 with databases to fetch or store information, enforces security rules, and handles complex business logic automatically. Without an application server, developers would have to manually manage low-level operations like threading, resource allocation, and security, which would make enterprise development far more complex. Difference Between a Web Server and an Application Server Although both web servers and application servers serve web content, their purposes are different. A web server (like Apache or Nginx) is designed mainly to deliver static content such as HTML, CSS, and images. It can handle simple websites but not complex applications. An application server, on the other hand, is designed to deliver dynamic content—that is, it can execute server-side logic, interact with databases, and generate real-time responses. For example, when you log in to a bank’s website and check your balance, the application server processes your request, verifies your credentials, retrieves data from the database, and sends the result back as a dynamic page. Hence, web servers are suitable for simple, static sites, while application servers power complex enterprise systems. Key Features of an Application Server Application servers in Java EE come with several built-in services that make them powerful and reliable. One of the most important features is component management, where the server automatically manages the lifecycle of enterprise components such as Servlets, EJBs, and Managed Beans. This includes their creation, initialization, and destruction. Another crucial feature is transaction management. In large enterprise systems, multiple actions often form part of a single transaction, such as transferring funds between accounts. The application server ensures that all these actions either succeed together or fail together, maintaining data integrity through ACID (Atomicity, Consistency, Isolation, Durability) properties. Security is another major feature of application servers. Through technologies like JAAS (Java Authentication and Authorization Service), the server authenticates users and ensures that only authorized individuals can access specific resources. It also manages resource pooling, which means it efficiently reuses database connections and threads to enhance performance and reduce overhead. Furthermore, application servers provide communication services using Java Messaging Service (JMS), SOAP, and RESTful Web Services, allowing integration with other systems or microservices. Finally, they offer load balancing and fault tolerance distributing workloads across multiple servers so that performance remains consistent even when the number of users increases dramatically. Architecture of an Application Server The architecture of a Java EE application server follows a multi-tier structure, which divides the application into different layers for better organization and scalability. The client tier is the topmost layer where users interact through web browsers, desktop software, or mobile Advanced Java Programming 248 applications. The web tier handles client requests and responses using technologies such as Servlets and JSP. The business tier contains the actual business logic, implemented through components like Enterprise JavaBeans (EJBs) or CDI-managed beans. Finally, the data tier interacts directly with databases using JPA or JDBC to perform operations like data retrieval and storage. Each of these tiers communicates with the others through well-defined interfaces, ensuring modularity, scalability, and maintainability. Fig. 3.2 Architecture of Application Server. Popular Java EE Application Servers There are several popular application servers available for Java EE (Jakarta EE), both opensource and commercial. GlassFish, developed initially by Sun Microsystems and now maintained by the Eclipse Foundation, is the official reference implementation of Jakarta EE. It fully supports all EE specifications and is often used for testing and learning purposes. WildFly (previously known as JBoss) is another widely used open-source application server developed by Red Hat. It is known for its high performance, lightweight modular design, and ability to handle large enterprise deployments. Another lightweight option is Apache TomEE, an enhanced version of Apache Tomcat that adds full Java EE features like EJB, JPA, and JAX-RS, making it ideal for smaller enterprise or RESTful web applications. Commercial options include IBM WebSphere and Oracle Advanced Java Programming 249 WebLogic, both of which are powerful, feature-rich servers used in industries that require high security and transaction reliability. WebSphere is often chosen by large financial institutions, while WebLogic is popular for enterprise resource planning (ERP) and customer relationship management (CRM) systems. A newer server, Payara, is derived from GlassFish and provides cloud-ready features suitable for modern microservices and container-based applications. Example of How an Application Server Works Consider a real-world example of an online shopping application built using Java EE. When a user opens the shopping site and logs in, the web layer of the application server (using Servlets or JSF) handles the request. The server then calls components in the business layer, such as EJBs, to validate the login credentials and retrieve user details. If the user adds items to the cart, the application server manages the session and stores this information securely. When the user proceeds to checkout, the server coordinates multiple actions verifying the payment, updating inventory, and confirming the order all within a single transaction to maintain consistency. If any part of the process fails (for example, a payment issue), the entire transaction is rolled back automatically. Throughout this process, the application server ensures proper resource management, transaction control, and data security without any manual coding by the developer. Advantages of Using an Application Server Application servers provide several advantages that make them indispensable for enterprise development. They greatly simplify development by managing low-level system details such as resource allocation, threading, and transactions. Developers can therefore focus on implementing business logic rather than infrastructure. They also offer portability, meaning a Java EE application can run on any compliant server without modification. Their built-in security features ensure safe data handling and access control, while their scalability and load balancing capabilities enable applications to serve thousands of users simultaneously without performance degradation. Moreover, the modular design of Java EE components allows for reusability and easy maintenance, making enterprise systems more cost-effective and efficient over time. Real-World Applications of Application Servers Application servers are used across a wide range of industries. In the banking sector, they manage secure financial transactions and customer data for online banking systems, such as those built on WebLogic or WebSphere. In e-commerce, they power online shopping portals like Amazon or Flipkart, handling product catalogs, payments, and user sessions efficiently using WildFly or TomEE. The healthcare industry uses application servers such as Payara or GlassFish to manage electronic medical records, patient data, and hospital operations securely. Government and public sector organizations also rely on application servers to Advanced Java Programming 250 provide stable, scalable platforms for citizen services such as tax filing, identity management, and e-governance systems. Containers The word container is used in two closely related ways in enterprise Java conversations, and it helps to keep them distinct. In Java EE / Jakarta EE, a container is a runtime environment provided by the application server that manages a specific kind of component (for example a Web container for Servlets/JSP/JSF or an EJB container for Enterprise JavaBeans). That container supplies services such as lifecycle management, dependency injection, transaction coordination, security, JNDI lookups and remote connectivity so developers can write business logic without implementing those low-level services themselves. In modern infrastructure and DevOps, containerization refers to packaging an application and everything it needs (libraries, configuration, binaries) into a lightweight, isolated runtime unit that runs on the host OS kernel. These OS-level containers (Docker, Podman, etc.) are designed for portability, fast startup, and consistent behavior across environments. Java EE “containers” what they provide and why they matter In the Java EE model, containers are logical environments inside the application server that host Java components and provide configurable, enterprise-grade services. When you assemble an application into Java EE modules and deploy them, each module is deployed into a matching container that handles the routine concerns for you. For example, a Web container instantiates and manages Servlets, maps URLs to those Servlets, manages HTTP sessions, and integrates with security realms. An EJB container instantiates EJBs, intercepts method calls to provide transactions and concurrency control, mediates security checks, and supplies remote invocation mechanics so a client can call an EJB as if it were local. Because the container supplies services such as transaction management, security enforcement, JNDI lookups, resource pooling (database connections, threads) and lifecycle callbacks, developers focus on business logic. Containers also let you configure behavior per environment: the same enterprise bean might run with stronger DB privileges in production and more restricted access in a test environment, simply by changing container configuration rather than code. In short, Java EE containers are the runtime scaffolding that makes component-based enterprise programming practical and portable. Example (Java EE): you deploy an EJB that implements order processing. The EJB container will automatically start/stop instances, manage transactions around business methods (so calls either commit or rollback as a unit), and apply security rules before letting a user invoke the method, Advanced Java Programming 251 OS-level containers (containerization) Containerization packages the application and its dependencies into an image that can be run as an isolated instance on any compatible host. The image contains only what the app needs (libraries, config, runtime) and shares the host operating system kernel with other containers. Because there is no full guest OS per container, containers are much lighter and faster to start than virtual machines. Containerization is widely used for microservices (each microservice runs in its own container), CI/CD pipelines (build once, test and deploy the same image everywhere), cloudnative apps (portable across cloud providers), dev/test environments (reproducible), and modernizing legacy apps (packaging old apps so they run consistently on new infra). Example (OS containers): you package a Java web application with a specific JDK and needed native libraries into a Docker image. That image runs the same on your laptop, a CI server, or a Kubernetes cluster eliminating “works on my machine” problems. How OS containers work the main building blocks At a high level, an OS container is nothing mystical: it is an isolated process (or set of processes) running on the host that has its own view of certain resources (filesystem, network interfaces, process IDs, etc.) and enforced resource limits. The container is launched from an image (a layered, read-only filesystem plus metadata and a command to run). The container runtime creates an isolated environment and runs the image’s command as a process in that environment. At a technical level this isolation and efficiency are achieved by several kernel features and supporting components:  Namespaces: kernel feature that gives a process its own isolated view of global system resources. There are namespaces for process IDs (PID), networking (NET), mount points (MNT), interprocess communication (IPC), UTS (hostname), and user IDs (USER). Namespaces make processes inside a container see their own PID tree, their own network interfaces, and their own mount points so they appear isolated from the rest of the system.  cgroups (control groups): kernel facility that limits and accounts for resource usage (CPU, memory, disk I/O, network) for a group of processes. cgroups ensure a container cannot consume all host resources and enable fine-grained resource quotas.  Union/overlay filesystems: container images are built as layered filesystems (each Dockerfile instruction adds a layer). Union filesystems (OverlayFS, AUFS) present these layers as one coherent filesystem to the container. Layering makes images compact and makes builds and caching efficient.  Container runtime and toolchain: tools like Docker, containerd, and CRI-O coordinate image creation, storage, and launching. They interface with the kernel (namespaces, cgroups) to set up the container environment. The Open Container Advanced Java Programming 252 Initiative (OCI) defines standard formats for images and runtimes so different tools interoperate.  Image registries: images are pushed to and pulled from registries (Docker Hub, private registries). The registry stores image layers and metadata so environments can fetch the exact artifact to run.  Network model: container runtimes create virtual network interfaces and bridge them to the host network or provide overlay/SDN networking (flannel, Calico) in distributed clusters. This gives containers IP addresses and controlled connectivity. So when you docker run an image, the runtime: (1) pulls the image layers, (2) creates a new writable layer for that container, (3) sets up namespaces and cgroups, (4) mounts the overlay filesystem, (5) sets up networking, and (6) executes the image’s entrypoint process in that isolated environment. Container image lifecycle and how you operate them Container usage typically follows a predictable lifecycle: build, store, distribute, run, monitor, and update. You author a Dockerfile (or similar) that describes how to build the image which base image to use, what files to copy, and which command to run. The image is built into layered artifacts and pushed to a registry. At deployment time the runtime pulls the image to the target host and instantiates one or many containers from that image. In production you don’t run containers manually for scale; you use an orchestrator (Kubernetes, Docker Swarm, or similar) that schedules containers across many hosts, performs health checks, restarts failed containers, and scales replicas up or down. Example workflow: Developers build an image for a Java microservice, push it to an internal registry as myorg/orders:1.2.0. The CI pipeline runs tests using that image. Kubernetes pulls myorg/orders:1.2.0 and creates pods, which the cluster exposes through a Service and an Ingress for external traffic. How Java EE containers relate to OS containers synergy and differences Although they share the same English word, Java EE containers and OS containers operate at different levels. Java EE containers are software runtime components inside an application server that provide services to Java components. OS containers are operating-system level process isolation units that package and run whole applications (including application servers). In modern deployments you commonly run a Java EE application server (GlassFish, WildFly, Payara, etc.) inside an OS container. That combines the enterprise services of the Java EE container with the portability and deployment benefits of OS containers. Running a Java EE app inside OS containers makes it easier to scale the server or migrate between environments, while the Java EE container continues to provide transaction, security, and lifecycle services inside each container instance. Advanced Java Programming 259 The Servlet API provides specific lifecycle methods that a developer can override to perform initialization, request handling, and cleanup tasks. Understanding this cycle is essential for developing efficient and resource-safe web applications. A typical Servlet Life Cycle begins when an HTTP request from a client (such as a web browser) reaches the web server. The web server then delegates this request to the Servlet Container, which is responsible for managing all servlet-related operations. When the container receives the request, it first checks whether the corresponding servlet class is already loaded into memory. If it is not, the container loads and instantiates the servlet before invoking its service() method. This method is the core of the servlet, responsible for handling client requests and generating appropriate responses. The servlet container efficiently manages multiple client requests by creating a separate thread for each request. Each thread executes the service() method of the same servlet instance concurrently. This multithreaded approach allows the servlet to process many requests at the same time, ensuring high performance and responsiveness. Fig. 3.5 Typical servlet life-cycle scenario. Advanced Java Programming 260 Main Stages of Servlet Life Cycle The life cycle of a servlet consists of four major stages: 1. Loading the Servlet 2. Initializing the Servlet 3. Handling Client Requests 4. Destroying the Servlet These stages are managed by the Servlet Container, which ensures that the servlet runs efficiently, serving multiple client requests concurrently. 1. Loading a Servlet The first step in the life cycle is loading and instantiation. When the web application starts, or when the servlet is requested for the first time, the servlet container performs the following tasks:  Class Loading: The servlet container loads the servlet class into memory using the class loader.  Instantiation: The container creates an instance of the servlet by invoking its noargument constructor. A servlet can be loaded in two ways:  Eager Loading: The servlet is loaded during server startup if configured with the <load-on-startup> element in web.xml.  Lazy Loading: The servlet is loaded only when it receives the first client request. At this stage, the servlet object is created but not yet initialized. 2. Initializing a Servlet After the servlet is instantiated, the container calls its init() method to perform initialization. This method is invoked only once in the entire lifecycle of the servlet. Purpose: The init() method is used to perform one-time setup tasks such as:  Establishing database connections  Loading configuration data  Initializing resources needed throughout the servlet’s lifetime Advanced Java Programming 261 Syntax: public void init() throws ServletException { // Initialization code (executed once) } Key Points:  Called once after servlet creation.  Can throw ServletException in case of initialization failure.  Initialization should not be done in the constructor because constructors cannot throw checked exceptions. Example: @Override public void init() throws ServletException { System.out.println("Servlet initialized successfully."); } After initialization, the servlet is ready to handle incoming requests. 3. Handling Client Requests Once the servlet is initialized, it begins processing client requests. This stage continues until the servlet is removed from memory. Request and Response Objects Each client request is represented by a ServletRequest object, and the corresponding response is represented by a ServletResponse object. For HTTP-based applications, these are specialized as:  HttpServletRequest  HttpServletResponse The servlet container creates these objects for every request and passes them to the servlet’s service() method. The service() Method The service() method is the core of the servlet. It receives the request, processes it, and sends a response back to the client. Advanced Java Programming 262 Syntax: public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { // Request handling logic } In the case of an HTTP servlet, the service() method automatically determines the type of request (GET, POST, PUT, DELETE) and delegates it to the corresponding method:  doGet() → Handles HTTP GET requests.  doPost() → Handles HTTP POST requests.  doPut() → Handles HTTP PUT requests.  doDelete() → Handles HTTP DELETE requests. Each request is typically handled in a separate thread, allowing multiple clients to interact with the servlet concurrently. Example: @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<h3>Welcome! This is a GET request handled by Servlet.</h3>"); } 4. Destroying a Servlet The final stage occurs when the servlet container decides to remove the servlet from memory usually during application shutdown or when resources must be freed. The container first waits for all active request threads to finish and then calls the destroy() method. Purpose: To release or close resources acquired during initialization, such as:  Database connections  File handles  Network sockets  Cached objects or threads Advanced Java Programming 263 Syntax: public void destroy() { // Cleanup code } After executing this method, the servlet instance becomes eligible for garbage collection by the JVM. Example: @Override public void destroy() { System.out.println("Servlet is being destroyed. Resources released."); } 3.4 Handling Client Requests and Responses When developing web applications in Java, one of the most crucial concepts is understanding how to handle client requests and server responses. The communication between a client (such as a web browser, mobile app, or REST API consumer) and a server takes place using the HTTP protocol. This process typically involves the client sending a request message, the server interpreting it, performing necessary operations, and then generating a structured response. In Java, this process is managed by the Servlet API, which provides a standard mechanism for creating web applications. A servlet acts as the controller that handles all the incoming client requests, processes them using application logic, and returns responses in the form of HTML pages, JSON data, XML, or any other suitable format. Frameworks like Spring Boot are built on top of the servlet architecture. They simplify the request–response mechanism through annotations and abstractions, but underneath, the same servlet principles apply. The Role of the Servlet Container Before understanding how requests and responses are handled, it is important to understand the Servlet Container (also known as the Web Container). The servlet container is a part of the Java Web Server (such as Apache Tomcat, Jetty, or GlassFish) that is responsible for: 1. Managing the lifecycle of servlets (initialization, execution, and destruction). 2. Mapping incoming client requests to appropriate servlets based on configuration. 3. Creating and managing objects like HttpServletRequest and HttpServletResponse. Advanced Java Programming 264 4. Handling multithreading by assigning each request to a new or existing thread. Whenever a client sends an HTTP request to a server, the servlet container performs the following sequence:  It receives the request from the web server.  It identifies which servlet should handle the request based on the URL pattern defined in the web.xml deployment descriptor or servlet annotations.  It creates instances of HttpServletRequest and HttpServletResponse objects.  It passes these objects to the appropriate method of the servlet, such as doGet() or doPost().  After the servlet finishes processing, the container sends the response back to the client. This structure ensures that developers can focus on application logic, while the container manages complex networking, threading, and protocol details. The HttpServletRequest Object The HttpServletRequest object represents the client’s request. It is created automatically by the servlet container and passed to the servlet when a request arrives. This object encapsulates all the data sent from the client and provides several methods to access it. Some of the most important data that can be retrieved from this object include:  HTTP Method: Using request.getMethod() you can determine whether the request is a GET, POST, PUT, or DELETE request.  Request Parameters: Parameters passed via URL query strings or HTML form data can be retrieved using request.getParameter("paramName"). For example, if the URL is /hello?name=John, then request.getParameter("name") returns John.  Headers: HTTP headers contain meta-information about the request. You can access them with request.getHeader("User-Agent") or request.getHeaderNames().  Body Content: In POST or PUT requests, clients often send large data or JSON payloads. The body can be read using request.getReader() for text or request.getInputStream() for binary content.  Cookies and Sessions: The getCookies() and getSession() methods help manage user state and track interactions across multiple requests.  Client Information: Details such as the client’s IP address or hostname can be accessed with getRemoteAddr() and getRemoteHost(). Thus, the HttpServletRequest object serves as a gateway to all incoming data from the client. Advanced Java Programming 265 Processing the Request in the Servlet Once the servlet receives the HttpServletRequest and HttpServletResponse objects, it begins processing the request. A servlet extends the HttpServlet class and overrides one or more of its lifecycle methods to handle specific types of HTTP requests. The most commonly used methods include:  doGet(): Handles HTTP GET requests, which are generally used to retrieve data or display resources.  doPost(): Handles POST requests, typically used to send data to the server, such as form submissions or JSON objects.  doPut(): Used to update existing data on the server.  doDelete(): Used to remove resources. When the servlet container receives a request, it determines which HTTP method is used and calls the corresponding method on the servlet. Inside these methods, developers write logic to perform the required actions, such as database queries, file handling, data validation, or computation. For example, if a user submits a form with their name, the servlet can extract the name parameter and use it to display a personalized message or store it in a database. The HttpServletResponse Object After processing the client request, the servlet prepares an appropriate response using the HttpServletResponse object. This object allows the servlet to control every aspect of the HTTP response that will be sent back to the client. The main functionalities of the HttpServletResponse object include:  Setting the Status Code: Using response.setStatus(HttpServletResponse.SC_OK) or response.sendError(HttpServletResponse.SC_NOT_FOUND) to inform the client about the result of their request.  Setting Headers: Response headers provide additional metadata about the response. For instance, response.setHeader("Cache-Control", "no-cache") prevents caching, while response.setContentType("application/json") specifies that the response contains JSON data.  Writing Content: To send content to the client, the servlet uses a PrintWriter for textbased data or a ServletOutputStream for binary data. For example, response.getWriter().println("<h1>Hello User</h1>") sends HTML content.  Redirection: If the servlet needs to redirect the client to another resource, it can call response.sendRedirect("/home").  Cookies and Sessions: The servlet can add cookies to the response using response.addCookie() for managing user sessions or preferences. Advanced Java Programming 266 In short, HttpServletResponse allows complete control over the output sent to the client, including status, headers, and content. Example: A Complete Request-Response Flow Here is an example that shows the complete process of handling a client request and generating a response using servlets. import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/greet") public class GreetingServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); String name = request.getParameter("name"); if (name == null || name.isEmpty()) { name = "Guest"; } PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h2>Welcome, " + name + "!</h2>"); out.println("</body></html>"); } } In this example, when a client visits http://localhost:8080/greet?name=Prithiga, the servlet receives the request, extracts the “name” parameter, and sends back an HTML response containing the greeting. The servlet container automatically handles the creation and destruction of the request and response objects. Handling Requests and Responses in Spring Boot Spring Boot builds upon the Servlet API to make development simpler and faster. Instead of manually working with HttpServletRequest and HttpServletResponse, developers use annotations that tell Spring Boot how to handle requests. Advanced Java Programming 267 For example, a simple controller in Spring Boot looks like this: import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String sayHello(@RequestParam String name) { return "Hello, " + name + "!"; } } When the client sends a request like /hello?name=Priya, Spring Boot automatically maps it to the sayHello() method, retrieves the “name” parameter, and returns the response as plain text. Spring Boot also provides features like automatic JSON conversion, exception handling, and response status management using annotations like @ResponseStatus and @ExceptionHandler. This allows developers to focus on business logic rather than low-level HTTP details. Managing Sessions and Cookies Sessions and cookies are essential for maintaining user state in web applications. A session allows the server to store information for a user between multiple requests. For example, once a user logs in, their session can store their username or user ID. This is achieved using the HttpSession object. Cookies are small text files stored on the client’s machine. They can store information such as preferences or authentication tokens. In servlets, cookies are created and added to responses using the Cookie class and retrieved using the getCookies() method of HttpServletRequest. Using both sessions and cookies properly enables smooth and personalized user experiences while ensuring secure communication between clients and servers. Error Handling and HTTP Status Codes When a servlet processes a request, various situations can arise such as missing resources or invalid input. Proper error handling is crucial. The servlet can send error codes and messages using methods like response.sendError(404, "Page Not Found"). Advanced Java Programming 268 Spring Boot simplifies error handling through global exception management. Developers can define a centralized class annotated with @ControllerAdvice that catches exceptions across the application and returns appropriate HTTP status codes and messages. Security Considerations Security is an integral part of handling client requests. Every piece of data received from the client must be validated to prevent attacks like cross-site scripting (XSS) or SQL injection. Cookies should be marked as secure and HTTP-only to prevent unauthorized access. Developers should also ensure all communication takes place over HTTPS. Frameworks like Spring Security further enhance application protection through authentication and authorization mechanisms. Working with Databases through Servlets Servlets are a vital component of Java web applications, enabling dynamic web pages that interact with databases. When a user interacts with a web interface, such as filling out a form or requesting data, the servlet acts as a bridge between the client and the database. To achieve this connection, Java uses JDBC (Java Database Connectivity), a standard API that provides methods to connect to a database, execute SQL queries, and retrieve or manipulate results. By integrating JDBC with servlets, web applications can handle operations such as user authentication, registration, data retrieval, updating records, and generating reports dynamically, making the web application both powerful and interactive. Setting up the Environment Before starting database programming with servlets, it is important to set up the required environment properly. This includes a database system, such as MySQL or PostgreSQL, a JDBC driver, and a web server such as Apache Tomcat. If you are using PostgreSQL, you need to install the PostgreSQL JDBC driver, usually available as a JAR file named postgresql.jar. This file must be added to the WEB-INF/lib directory of your web application so that the servlet container can access it. When using Maven or Gradle, the dependency can be managed directly in your project configuration file, making setup easier. Database Preparation To demonstrate the connection, let us assume we are using a PostgreSQL database named mobile_store. Inside this database, we create a table named mobiles to store mobile phone details such as model name, brand, and price. Advanced Java Programming 275 Advantages of Using JDBC with Servlets Using JDBC within servlets provides several advantages: 1. It allows real-time interaction between the user interface and the backend database. 2. It ensures platform independence since JDBC supports multiple databases like MySQL, Oracle, and PostgreSQL. 3. It allows easy integration with frameworks like JSP and Spring MVC. 4. It supports transaction management and batch processing for high-performance applications. 3.5 Servlet Advanced Concepts: Request Dispatcher The RequestDispatcher interface provides a simple but powerful mechanism for servlet collaboration. It allows one servlet or other web resource to delegate processing to another resource or to include the output of another resource inside the response. The two core operations are forward and include. Forward hands off the entire request and response to another target resource so that the target produces the final response. Include invokes the target resource and merges its output into the response that the calling resource is building. RequestDispatcher therefore supports modular design and reuse of dynamic or static resources such as servlets, JSP pages and HTML files. How to obtain a RequestDispatcher object There are three common ways to obtain a RequestDispatcher object. The first is to ask the servlet request for a dispatcher by path. This path may be relative to the current servlet or start with a slash to indicate the context root. The second is to ask the servlet context for a dispatcher by path. The third is to ask the servlet context for a named dispatcher where the name is the name used when the servlet was registered. In code these three approaches look like this. // From the request using a context relative path RequestDispatcher rd1 = request.getRequestDispatcher("/welcome"); // From the servlet context using a path RequestDispatcher rd2 = getServletContext().getRequestDispatcher("/welcome"); // From the servlet context using a servlet name RequestDispatcher rd3 = getServletContext().getNamedDispatcher("welcomeServlet"); Choosing between these options depends on how you deploy and map your servlets. A path based lookup is the most common. A named lookup is useful when you want to decouple the caller from the actual URL mapping. Advanced Java Programming 276 Forward method and its semantics The forward method transfers control completely to the target resource. When a forward is invoked the servlet container stops writing any content from the calling servlet to the response buffer and calls the target resource with the same request and response objects. The HTTP status and headers are not committed before calling forward. If the response has already been committed to the client, calling forward will throw an IllegalStateException. Any request attributes set on the request are visible to the target. Because forward changes which resource writes the final response, the client URL remains the same unless the target itself issues a redirect. A typical use of forward is to validate user input in one servlet and then forward to a JSP for rendering. // In a validation servlet if (valid) { request.setAttribute("user", userObject); RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/profile.jsp"); rd.forward(request, response); // profile.jsp renders the page using request attributes } else { RequestDispatcher rd = request.getRequestDispatcher("/error.html"); rd.forward(request, response); } Note that forwarding to a path under WEB INF is a common pattern to prevent direct browser access to JSPs that should only be used for rendering. Include method and its semantics The include method invokes the target resource and inserts the target output into the response stream at the exact point where include is called. The calling servlet remains responsible for the overall response. The target resource can read request parameters and attributes and it may write content to the response. After include returns, the calling servlet can continue writing more content to the response. Include sets dispatcher type to INCLUDE and also exposes special request attributes so the included target can discover the include context. A common use of include is to assemble pages from reusable components such as headers, footers and navigation fragments. // In a servlet building a page response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html><body>"); RequestDispatcher header = request.getRequestDispatcher("/common/header.jsp"); header.include(request, response); // header output appears here Advanced Java Programming 277 out.println("<div class='main'>"); // main content out.println("</div>"); RequestDispatcher footer = request.getRequestDispatcher("/common/footer.jsp"); footer.include(request, response); // footer output appended out.println("</body></html>"); Include is also used when a servlet wants to reuse the output of another servlet or JSP without giving up control of the response. Relative and absolute paths and mapping rules When you obtain a RequestDispatcher using request.getRequestDispatcher with a path that does not start with a slash it is treated as relative to the current request path. When the path starts with a slash it is interpreted relative to the current context root. Calling getServletContext().getRequestDispatcher requires a path that starts with a slash and is always relative to the context root. These rules affect how you structure your application and where you place JSPs and static resources. Request attributes versus parameters and scope implications RequestDispatcher forwards and includes share the same HttpServletRequest object. That means request attributes are a safe mechanism to pass data between the original servlet and the target resource. Parameters from the original HTTP request remain available to the target, but attributes provide a convenient programmatic channel for objects such as domain models, error messages or form data. For example: request.setAttribute("errors", errorList); RequestDispatcher rd = request.getRequestDispatcher("/login.jsp"); rd.forward(request, response); On the other hand session attributes are stored at session scope and persist across multiple requests. Use request attributes for short lived data that belongs to a single request processing cycle. Error handling and common exceptions Forward must be invoked before the response is committed. If the servlet or container has already flushed the response headers to the client, calling forward will throw IllegalStateException. Include may be used after partial output, but it is good practice to control when content is committed by setting response buffer size or by avoiding direct flush calls. All dispatch operations can throw ServletException or IOException, so typical code wraps calls in try catch and either logs or forwards to an error page. Advanced Java Programming 278 try { RequestDispatcher rd = request.getRequestDispatcher("/target"); rd.forward(request, response); } catch (ServletException | IOException e) { log("Dispatch failed", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } Practical example with login and messages Below is a concise login example using both include and forward semantics. The login servlet validates credentials and forwards to a welcome servlet on success. On failure it writes an inline message and then includes the original form so the user can retry. // LoginServlet doPost String name = request.getParameter("userName"); String password = request.getParameter("userPass"); if ("admin".equalsIgnoreCase(name) && "admin".equals(password)) { request.setAttribute("userName", name); RequestDispatcher rd = request.getRequestDispatcher("/welcome"); rd.forward(request, response); } else { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<p>Sorry incorrect user name or password please try again</p>"); RequestDispatcher rd = request.getRequestDispatcher("/index.html"); rd.include(request, response); } The welcome servlet reads the request attribute or parameter and produces the final greeting. // WelcomeServlet doGet or doPost String name = (String) request.getAttribute("userName"); if (name == null) { name = request.getParameter("userName"); } response.getWriter().println("Welcome " + (name == null ? "Guest" : name)); RequestDispatcher related request attributes and error dispatch attributes The servlet specification defines a set of request attribute names that are automatically set during forward and include operations and during error dispatch. These attributes allow the target to inspect the original request context. For forward the container sets attributes such as those for the original request URI, original context path and original servlet path. For include Advanced Java Programming 279 the container sets attributes that indicate the include request URI and include servlet path. During error dispatch the container makes the exception, the status code and the original request URI available under standardized attribute names. These attribute names are available as constants documented in the servlet API for use by filters and error pages. Examples of typical attribute names you may inspect include names that represent original request URI or error exception. Reading those attributes lets an error page display which resource caused the problem or log a full diagnostic. When to prefer forward versus redirect Forward keeps processing on the server and the client URL remains unchanged. Use forward to render views after server side processing or to chain processing between components. Redirect uses response.sendRedirect and sends a 302 status to the client asking the browser to issue a new request to a different URL. Redirect is required if you want the browser address bar to change or if you want to implement the Post redirect Get pattern to avoid duplicate form submission. Use redirect when the new location is in another application or when you want the client to see the new URL. Security and design considerations When forwarding to JSPs or other resources avoid forwarding to resources that the client could access directly if you want to enforce server side control. Placing view JSPs under WEB INF prevents direct browser access while allowing forwards from servlets. Avoid exposing sensitive data by not forwarding request attributes to untrusted components. Validate input and sanitize any data rendered by included resources to prevent cross site scripting attacks. 3.6 Session Management Session management means keeping track of a user across multiple requests so the application can remember who the user is and what they were doing. Web traffic is inherently stateless which means each HTTP request stands alone. Session management restores continuity by associating a unique session with a client and storing data on the server side tied to that session. The servlet API exposes this capability through the HttpSession object which is the central mechanism for storing and retrieving user specific data across several page visits. A session is a server side data container created when a user first interacts with the application. The servlet container generates a unique session identifier and typically provides it to the client in a cookie. On each subsequent request the container reads the identifier and loads the corresponding HttpSession object. Sessions are important because they allow user authentication to persist across pages, they let the server store temporary preferences and shopping cart contents, and they reduce friction by avoiding repeated logins or repeated data Advanced Java Programming 280 entry. Sessions are also a boundary for applying security rules and for implementing logout or automatic expiration. How to obtain and use HttpSession The servlet API makes session handling simple. Call request.getSession to obtain the current session or to create a new one if none exists. Calling request.getSession(false) returns null if there is no existing session. Calling request.getSession(true) or request.getSession with no argument forces creation when necessary. Example of creating a session and setting attributes: HttpSession session = request.getSession(); // creates if absent session.setAttribute("username", "alice"); session.setAttribute("cartSize", Integer.valueOf(3)); Example of reading and removing attributes: HttpSession session = request.getSession(false); // do not create if (session != null) { String username = (String) session.getAttribute("username"); session.removeAttribute("cartSize"); } Example of invalidating a session: HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); // destroys session and all attributes } Example of changing session timeout: HttpSession session = request.getSession(); session.setMaxInactiveInterval(30 * 60); // thirty minutes in seconds These examples show how attributes provide an easy way to pass Java objects between servlets and JSP pages during the life of a session. Common session tracking mechanisms Cookies are the standard way to convey the session identifier from client to server. The container issues a cookie named JSESSIONID by default that contains the session id. If the client returns that cookie on subsequent requests, the container maps the id to the server side session object. Advanced Java Programming 281 When cookies are not available there are two fallback options. One option is URL rewriting where the container appends the session id to links generated by response.encodeURL or response.encodeRedirectURL. The other option is hidden form fields where the id is embedded in forms and posted back. URL rewriting works for standard hyperlinks and form submissions but it exposes the session id in the URL which has security implications. Hidden form fields only work for form submissions and therefore are not a general replacement for cookies. Example of URL rewriting: String url = response.encodeURL("profile"); out.println("<a href=\"" + url + "\">View Profile</a>"); A typical login example with secure session handling When implementing login it is best practice to avoid session fixation attacks by invalidating any existing session and creating a new one after authentication. Store the minimal required data and avoid putting large objects or sensitive data that does not need to remain in memory. Login servlet example that protects against session fixation: // after verifying credentials HttpSession oldSession = request.getSession(false); if (oldSession != null) { oldSession.invalidate(); } HttpSession session = request.getSession(true); // new session session.setAttribute("username", username); session.setMaxInactiveInterval(20 * 60); // 20 minutes response.sendRedirect("dashboard"); This pattern ensures the session id used before authentication cannot be reused by an attacker. Using sessions for CSRF protection Sessions are commonly used to store a CSRF token that a page renders into forms as a hidden field. When the form is submitted the server compares the token in the request with the token held in the HttpSession. If they match the request is valid. Example token generation and validation: // token generation when rendering form HttpSession session = request.getSession(); String token = UUID.randomUUID().toString(); session.setAttribute("csrfToken", token); Advanced Java Programming 282 out.println("<input type='hidden' name='csrfToken' value='" + token + "'/>"); // token validation in form handler String tokenFromForm = request.getParameter("csrfToken"); String tokenInSession = (String) request.getSession(false).getAttribute("csrfToken"); if (tokenInSession == null || !tokenInSession.equals(tokenFromForm)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "Possible CSRF detected"); } Storing the token in session keeps the secret server side while the form provides the value for comparison. Session security considerations Mark session cookies with HttpOnly and Secure flags to reduce risk. HttpOnly prevents client side scripts from reading the cookie, which mitigates some cross site scripting attacks. Secure ensures the cookie is sent only over HTTPS. Many containers allow enabling these flags in configuration, or you can set a cookie manually. Example setting a cookie manually with security flags: Cookie cookie = new Cookie("JSESSIONID", session.getId()); cookie.setHttpOnly(true); cookie.setSecure(true); cookie.setPath(request.getContextPath()); response.addCookie(cookie); Always use HTTPS for authenticated sessions and avoid exposing session ids in URLs whenever possible. Regenerate session ids on privilege changes such as login, and clear sensitive data from session attributes before invalidation. Session size, scalability and distributed environments Sessions are stored on the server and can consume memory if they hold large objects or many concurrent users exist. Avoid placing big objects in session. For scalable applications deployed to multiple nodes in a cluster you have several choices. You can use sticky sessions so a user is always routed to the same server node. You can use session replication where the container replicates session data to other nodes. You can externalize sessions into a shared store such as an in memory datastore like Redis. Externalizing sessions is commonly used for cloud or container deployments because it decouples session lifetime from any single application instance and supports horizontal scaling. Advanced Java Programming 283 Example of session lifecycle in a servlet scenario When a user visits the site for the first time and calls request.getSession, the container generates an id and creates a new HttpSession. The servlet populates attributes with user specific values. As the user navigates the site the container locates the same session using the session id from cookie or URL rewriting. If the user is inactive longer than the configured timeout the container invalidates the session automatically and fires sessionDestroyed events for listeners. If the application explicitly calls session.invalidate the session is immediately removed and any subsequent request will have no associated session unless a new one is created. Create sessions only when necessary. Use request.getSession(false) when simply checking for authentication to avoid creating many unused sessions. Store primitives and small value objects rather than large data. Invalidate sessions on logout and after major account changes. Regenerate session id on login to prevent fixation attacks. Set cookie flags HttpOnly and Secure and always use HTTPS for authenticated endpoints. Consider an external session store for distributed architectures. 3.7 Cookies and Http Session Interface Cookies In web applications, cookies are one of the most important techniques for maintaining state between client and server. The HTTP protocol itself is stateless every request sent from a client (like a browser) is treated as a completely new one by the server. To make a web application remember a user’s information across multiple requests, cookies are used. A cookie is a small piece of textual information (a key–value pair) stored by the browser on behalf of the server. When the client makes a request for the first time, the server sends a cookie back with the response. This cookie is stored in the client’s browser. On every subsequent request to the same domain, the browser automatically includes the cookie in the request headers. This allows the server to recognize the returning client and retrieve stored information about the session, preferences, or user data. How Cookies Work When a client (browser) first sends a request to a web server, the server can attach a cookie to the HTTP response. The browser then stores this cookie locally. For example, the HTTP response header might look like this: Set-Cookie: userId=101; Max-Age=3600; Path=/; HttpOnly Advanced Java Programming 284 On subsequent requests to the same server, the browser sends: Cookie: userId=101 This way, the server identifies that this request belongs to the same user who previously logged in or performed a specific action. If the cookie’s ID matches an existing record, the server treats it as an old user session. If no matching cookie is found, the request is treated as new. The Cookie Class in Java In Java Servlets, cookies are handled using the Cookie class which is part of the javax.servlet.http package. Creating, sending, and reading cookies in Java is simple because the servlet container automatically manages cookie headers in HTTP requests and responses. Creating a cookie: Cookie cookie = new Cookie("user", "JohnDoe"); response.addCookie(cookie); Retrieving cookies: Cookie[] cookies = request.getCookies(); for (Cookie c : cookies) { if (c.getName().equals("user")) { String value = c.getValue(); } } The servlet API provides methods to set, get, and manage cookies conveniently. Important Methods in the Cookie Class Method Description getName() Returns the name of the cookie getValue() Returns the cookie value setValue(String value) Changes the value of the cookie getMaxAge() Returns the maximum age in seconds setMaxAge(int seconds) Defines how long the cookie should be stored Advanced Java Programming 291 String name = request.getParameter("userName"); out.println("<h1>Welcome, " + name + "</h1>"); // Create or get existing session HttpSession session = request.getSession(); // Set attribute in session session.setAttribute("uname", name); out.println("<a href='servlet2'>Visit Servlet 2</a>"); out.close(); } } Second.java import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Second extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); // Fetch existing session HttpSession session = request.getSession(false); if (session != null) { String name = (String) session.getAttribute("uname"); out.println("<h2>Hello again, " + name + "!</h2>"); } else { out.println("<h2>No session found. Please start from the home page.</h2>"); } out.close(); } } web.xml <web-app> <servlet> Advanced Java Programming 292 <servlet-name>s1</servlet-name> <servlet-class>First</servlet-class> </servlet> <servlet-mapping> <servlet-name>s1</servlet-name> <url-pattern>/servlet1</url-pattern> </servlet-mapping> <servlet> <servlet-name>s2</servlet-name> <servlet-class>Second</servlet-class> </servlet> <servlet-mapping> <servlet-name>s2</servlet-name> <url-pattern>/servlet2</url-pattern> </servlet-mapping> </web-app> Output Description 1. Step 1 – index.html: The user enters their name and clicks “Submit”. Output: Welcome, Greenfield! Visit Servlet 2 2. Step 2 – servlet2 (Second.java): When the user clicks the “Visit Servlet 2” link, the second servlet fetches the session created earlier and retrieves the stored username. Output: Hello again, Greenfield! 3. If the session has expired or user opens Servlet2 directly: No session found. Please start from the home page. Working with Files Working with files is an essential capability in most applications. Files are used to store configuration data, logs, user uploads, cached results, exported reports, and much more. Java gives several complementary APIs for file I O ranging from the classic java.io streams to the Advanced Java Programming 293 modern java.nio.file utilities. Below I explain concepts, patterns, pitfalls, and practical examples you can reuse in real projects. Types of file access and when to use them File access falls into a few broad categories. Text file processing is for human readable data such as CSV, logs, or plain configuration. Binary file processing is for images, archives, or serialized objects. Random access is needed when you must read or write arbitrary positions inside a file, for example with indexes or resumable downloads. Streaming is important for very large files that cannot be fully loaded into memory. Choose the approach that matches your size constraints and performance needs. Paths and the file system API Use java.nio.file.Path and java.nio.file.Files for most file system operations. Path is superior to the older java.io.File because it is immutable, composable, and integrates with a richer set of operations. Example import java.nio.file.*; Path dir = Paths.get("/data/app"); Path file = dir.resolve("users.csv"); // safe composition boolean exists = Files.exists(file); Key useful Files methods include createDirectories, exists, readAllLines, write, copy, move, delete, and probeContentType. Many of these throw IOException, so handle or propagate appropriately. Reading and writing small text files For small files where memory is not a concern, Files.readAllLines and Files.write are concise and robust. Always specify a charset when working with text. Example Path path = Paths.get("config/settings.txt"); List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8); List<String> out = List.of("settingA=true", "settingB=42"); Files.write(path, out, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); Advanced Java Programming 294 Streaming large files For large files stream them rather than load them fully into memory. Use BufferedReader for text and InputStream for binary. Try with resources ensures streams are closed automatically. Text streaming example try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { String line; while ((line = br.readLine()) != null) { process(line); } } Binary streaming and copying try (InputStream in = Files.newInputStream(src); OutputStream out = Files.newOutputStream(dst, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { byte[] buffer = new byte[8192]; int n; while ((n = in.read(buffer)) > 0) { out.write(buffer, 0, n); } } Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING) is a ready made option when you only need to copy. Character encoding and line endings Always use a specified Charset like StandardCharsets.UTF_8. Relying on platform default charset leads to bugs when your code runs on different operating systems. Be aware of platform line ending differences. Use System.lineSeparator() when you generate text to be consumed on the same platform, but for cross platform format prefer \n and document the expected format. Random access and seeking Use RandomAccessFile or a FileChannel if you need to read or write at arbitrary offsets. FileChannel supports file locking and transfer to other channels. Example reading at offset try (RandomAccessFile raf = new RandomAccessFile("data.bin", "r")) { Advanced Java Programming 295 raf.seek(1024); byte[] buf = new byte[256]; int read = raf.read(buf); } FileChannel example with lock try (FileChannel ch = FileChannel.open(path, StandardOpenOption.WRITE)) { try (FileLock lock = ch.lock()) { // safe exclusive update } } Atomic updates and durability To avoid corrupted or partially written files use atomic replace patterns. Write to a temp file then move into place using Files.move(temp, target, ATOMIC_MOVE) when supported. This is important for config files and indexes. Example atomic write Path temp = Files.createTempFile(dir, "tmp-", ".tmp"); Files.write(temp, dataBytes); Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); Serialization and structured data For Java object persistence use JSON, XML or binary serialization. Java native serialization (ObjectOutputStream) is convenient but brittle and version sensitive. Prefer JSON with libraries such as Jackson or Gson for interoperability. JSON example with Jackson ObjectMapper mapper = new ObjectMapper(); try (OutputStream os = Files.newOutputStream(path)) { mapper.writeValue(os, somePojo); } File uploads and downloads in web applications In servlets handle uploads by streaming Part objects rather than buffering whole files in memory. Annotate the servlet with @MultipartConfig or use frameworks that abstract it. Advanced Java Programming 296 Upload servlet example @MultipartConfig(fileSizeThreshold = 1024 * 1024, // buffer threshold maxFileSize = 50L * 1024 * 1024, // 50 MB maxRequestSize = 60L * 1024 * 1024) @WebServlet("/upload") public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part filePart = req.getPart("file"); String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); try (InputStream in = filePart.getInputStream()) { Files.copy(in, Paths.get("/var/uploads").resolve(fileName), StandardCopyOption.REPLACE_EXISTING); } resp.getWriter().println("Uploaded " + fileName); } } Download servlet example protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Path file = Paths.get("/var/uploads/report.pdf"); resp.setContentType(Files.probeContentType(file)); resp.setHeader("Content-Disposition", "attachment; filename=\"report.pdf\""); try (OutputStream out = resp.getOutputStream(); InputStream in = Files.newInputStream(file)) { byte[] buf = new byte[8192]; int len; while ((len = in.read(buf)) > 0) out.write(buf, 0, len); } } When sending large files set buffer sizes appropriately and avoid response.getWriter which is for text. Security considerations Prevent directory traversal by validating or canonicalizing user provided paths. Never concatenate user input directly into file paths. Advanced Java Programming 297 Path validation example Path base = Paths.get("/var/uploads").toRealPath(); Path requested = base.resolve(userInput).normalize(); if (!requested.startsWith(base)) throw new SecurityException("Invalid path"); Check content type with Files.probeContentType and restrict uploads to allowed types. Use HttpOnly and Secure flags for cookies when you store file related tokens. Sanitize file names to avoid special characters. Limit file size to prevent denial of service. Consider virus scanning for uploaded content. Concurrency and locking Multiple threads may read or write files concurrently. For read only shared access is fine. For writes coordinate with locks. Use FileChannel.lock() for inter process locks, or external coordination like a database or distributed lock for cluster environments. Beware that file locks semantics differ by platform. Files in distributed systems When you deploy across multiple instances you should avoid storing important files only on local disk. Use shared storage such as S3 or NFS, or store metadata in a database and file contents in an object store. For session replication or temporary caching use Redis or another shared cache rather than local disk. Logging and audit trails When your application writes or deletes files keep an audit trail. Log the user id, timestamp, file path, and action type. This helps debugging and security forensics. Useful libraries and utilities Use Apache Commons IO for convenience helpers like IOUtils.copy, FileUtils.readFileToString, and FilenameUtils. Use Guava for some IO helpers if present. For CSV handling use OpenCSV or Apache Commons CSV. For streamed JSON use Jackson streaming API. Example with Commons IO try (InputStream in = Files.newInputStream(src); OutputStream out = Files.newOutputStream(dst)) { IOUtils.copy(in, out); } Advanced Java Programming 298 Nonblocking I/O in Servlets. Non-blocking input/output (I/O) in servlets is a powerful mechanism that allows web applications to perform data transmission and processing efficiently without tying up server threads while waiting for I/O operations to complete. In the traditional model, a servlet thread blocks while reading data from a client or writing data back to it. This approach works fine for small or simple interactions but becomes inefficient when handling large uploads, slow clients, or a high number of concurrent connections. Non-blocking I/O changes this behavior by letting the servlet handle I/O asynchronously, allowing the same number of threads to serve many more requests simultaneously. Traditional (Blocking) I/O vs Non-Blocking I/O In the blocking I/O model, when a servlet reads from a request stream or writes to a response stream, the operation halts the servlet thread until data is fully read or written. If a client is slow to send data or to receive the response, the thread remains idle, wasting valuable server resources. For example, if a servlet reads an uploaded file or sends a large report, each client consumes one thread for the entire duration of the data transfer. In non-blocking I/O, instead of waiting, the servlet registers interest in I/O events. When the container detects that data is available to read or that the output stream is ready to accept more data, it notifies the servlet through callback methods. During this time, the servlet thread is free to handle other requests, improving scalability and responsiveness. Asynchronous Processing Before performing non-blocking I/O, the servlet must support asynchronous execution. Asynchronous processing allows a servlet to release the request thread back to the container while the I/O operation continues in the background. To enable asynchronous support, a servlet can be declared as asynchronous either in the web deployment descriptor or by annotation: @WebServlet(urlPatterns = "/asyncDemo", asyncSupported = true) public class AsyncDemoServlet extends HttpServlet { // Implementation goes here } Once asynchronous mode is enabled, the servlet can start asynchronous processing using the request object: AsyncContext asyncContext = request.startAsync(); Advanced Java Programming 299 The AsyncContext object manages the lifecycle of the asynchronous operation, allowing the servlet to complete, dispatch, or time out the request later. This mechanism forms the foundation for non-blocking I/O. Non-Blocking Reading Using ReadListener When the servlet reads incoming data (such as a file upload or form submission), it can use a ReadListener to perform non-blocking reads from the request’s input stream. The container triggers specific methods of the ReadListener whenever data becomes available, has been completely read, or when an error occurs. Example: Reading Data Without Blocking @WebServlet(urlPatterns = "/readData", asyncSupported = true) public class NonBlockingReadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { AsyncContext asyncContext = request.startAsync(); ServletInputStream input = request.getInputStream(); input.setReadListener(new ReadListenerImpl(input, asyncContext)); } private static class ReadListenerImpl implements ReadListener { private final ServletInputStream input; private final AsyncContext asyncContext; private final StringBuilder data = new StringBuilder(); ReadListenerImpl(ServletInputStream input, AsyncContext asyncContext) { this.input = input; this.asyncContext = asyncContext; } @Override public void onDataAvailable() throws IOException { byte[] buffer = new byte[1024]; int bytesRead; while (input.isReady() && (bytesRead = input.read(buffer)) != -1) { data.append(new String(buffer, 0, bytesRead)); } } @Override public void onAllDataRead() throws IOException { HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse(); Advanced Java Programming 300 response.setContentType("text/plain"); response.getWriter().write("Received: " + data.toString()); asyncContext.complete(); } @Override public void onError(Throwable throwable) { throwable.printStackTrace(); asyncContext.complete(); } } } Explanation 1. The servlet enables asynchronous support. 2. When a POST request is received, the servlet retrieves the ServletInputStream from the request. 3. The servlet assigns a custom ReadListener to handle input events. 4. The listener methods (onDataAvailable(), onAllDataRead(), and onError()) are automatically invoked by the container:  onDataAvailable() executes when part of the data is ready for reading.  onAllDataRead() executes once the entire body is read.  onError() handles I/O errors. 5. During all these operations, no servlet thread remains blocked waiting for input. Non-Blocking Writing Using WriteListener For sending large amounts of data (for example, streaming files or long reports), servlets can use WriteListener with the response’s output stream. This mechanism ensures that the servlet writes data only when the client is ready to receive it. Example: Writing Data Without Blocking @WebServlet(urlPatterns = "/writeData", asyncSupported = true) public class NonBlockingWriteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { AsyncContext asyncContext = request.startAsync(); ServletOutputStream output = response.getOutputStream(); // Prepare large data List<String> messages = new ArrayList<>(); for (int i = 1; i <= 1000; i++) { Advanced Java Programming 307 Download Servlet (DownloadServlet.java) This servlet streams a requested file back to the client. It uses FileInputStream and writes to response.getOutputStream() with proper headers. It validates the filename and enforces that files are served only from the upload directory. package com.example.filedemo; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.*; @WebServlet("/download") public class DownloadServlet extends HttpServlet { private static final String UPLOAD_DIR = "/tmp/uploads"; // must match UploadServlet @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String requested = request.getParameter("file"); if (requested == null || requested.isBlank()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing file parameter"); return; } // Decode and sanitize String fileName = new File(requested).getName(); // this strips any path info File file = new File(UPLOAD_DIR, fileName); // Validate file exists and is a file if (!file.exists() || !file.isFile()) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found"); return; } // Set content type and headers String mime = getServletContext().getMimeType(file.getName()); if (mime == null) mime = "application/octet-stream"; Advanced Java Programming 308 response.setContentType(mime); response.setContentLengthLong(file.length()); // Force download dialog response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\""); // Stream file content via FileInputStream try (FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); OutputStream os = response.getOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(os)) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); } bos.flush(); } catch (IOException e) { // If client aborts connection, writing will fail; just log or ignore log("Error streaming file " + file.getAbsolutePath(), e); } } } Example Output 1. Browser after upload (UploadServlet response) File uploaded successfully Saved as: localfile.txt [Download localfile.txt] 2. Browser when downloading (download dialog or saved file) The browser prompts to save or directly opens the file depending on MIME type and settings. The downloaded file matches the uploaded bytes exactly. 3. curl output for download $ curl -v -o downloaded.txt "http://localhost:8080/app/download?file=localfile.txt" <... HTTP/1.1 200 OK ...> downloaded.txt equals the original. Advanced Java Programming 309 Result The servlet program for file upload and download using @MultipartConfig and FileOutputStream was successfully implemented, executed, and tested. 2. Develop a servlet to handle HTTP GET and POST methods. Accept form data (e.g., user registration) and display it. Aim To develop a servlet that handles HTTP GET and POST methods. The servlet will present a user registration form on GET and accept the submitted form data on POST, then display the submitted user details back to the client. Procedure 1. Create a dynamic web project or a Maven webapp. 2. Add servlet API dependency if using Maven. 3. Create an HTML registration form that sends data with method POST. Optionally let the servlet render the form on GET. 4. Implement a servlet that overrides doGet() to show the form and doPost() to process the form data. 5. Validate and sanitize user inputs. 6. Display the registration details in an HTML response. 7. Deploy to a servlet container such as Tomcat and test using a browser and curl. HTML Form (registration.html) Place this file in the webapp root (or have doGet generate the same form). <!doctype html> <html> <head> <meta charset="utf-8"/> <title>User Registration</title> </head> <body> <h2>User Registration Form</h2> <form action="register" method="post"> Full Name: <input type="text" name="fullname" required /><br/><br/> Email: <input type="email" name="email" required /><br/><br/> Password: <input type="password" name="password" required /><br/><br/> Gender: <input type="radio" name="gender" value="Male" checked/> Male Advanced Java Programming 310 <input type="radio" name="gender" value="Female" /> Female <input type="radio" name="gender" value="Other" /> Other <br/><br/> Hobbies: <input type="checkbox" name="hobby" value="Reading" /> Reading <input type="checkbox" name="hobby" value="Traveling" /> Traveling <input type="checkbox" name="hobby" value="Gaming" /> Gaming <br/><br/> Country: <select name="country"> <option value="India">India</option> <option value="USA">USA</option> <option value="UK">UK</option> <option value="Other">Other</option> </select> <br/><br/> <input type="submit" value="Register" /> </form> </body> </html> ] Servlet Program (RegisterServlet.java) This servlet shows the form on GET and processes it on POST. It demonstrates basic validation and displays the submitted data (except password is masked when displayed for safety). package com.example.registration; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; import java.util.Arrays; @WebServlet(name = "RegisterServlet", urlPatterns = {"/register"}) public class RegisterServlet extends HttpServlet { Advanced Java Programming 311 // Show the registration form (GET) @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) { out.println("<!doctype html><html><head><meta charset='utf-8'/><title>User Registration</title></head><body>"); out.println("<h2>User Registration Form</h2>"); out.println("<form method='post' action='register'>"); out.println("Full Name: <input type='text' name='fullname' required /><br/><br/>"); out.println("Email: <input type='email' name='email' required /><br/><br/>"); out.println("Password: <input type='password' name='password' required /><br/><br/>"); out.println("Gender: <input type='radio' name='gender' value='Male' checked/> Male "); out.println("<input type='radio' name='gender' value='Female'/> Female "); out.println("<input type='radio' name='gender' value='Other'/> Other<br/><br/>"); out.println("Hobbies: <input type='checkbox' name='hobby' value='Reading'/> Reading "); out.println("<input type='checkbox' name='hobby' value='Traveling'/> Traveling "); out.println("<input type='checkbox' name='hobby' value='Gaming'/> Gaming<br/><br/>"); out.println("Country: <select name='country'>"); out.println("<option>India</option><option>USA</option><option>UK</option><option >Other</option>"); out.println("</select><br/><br/>"); out.println("<input type='submit' value='Register'/>"); out.println("</form>"); out.println("</body></html>"); } } // Process registration data (POST) @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Ensure correct character encoding for form parameters request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); // Retrieve parameters String fullName = safe(request.getParameter("fullname")); String email = safe(request.getParameter("email")); String password = request.getParameter("password"); // do not echo raw password Advanced Java Programming 312 String gender = safe(request.getParameter("gender")); String[] hobbies = request.getParameterValues("hobby"); // may be null String country = safe(request.getParameter("country")); // Simple server side validation StringBuilder errors = new StringBuilder(); if (fullName == null || fullName.isBlank()) errors.append("Full name is required.<br/>"); if (email == null || email.isBlank()) errors.append("Email is required.<br/>"); if (password == null || password.length() < 6) errors.append("Password is required and must be at least 6 characters.<br/>"); try (PrintWriter out = response.getWriter()) { out.println("<!doctype html><html><head><meta charset='utf8'/><title>Registration Result</title></head><body>"); out.println("<h2>Registration Result</h2>"); if (errors.length() > 0) { // Show errors and a link back out.println("<div style='color:red'><strong>Errors:</strong><br/>" + errors.toString() + "</div>"); out.println("<p><a href='register'>Go back to form</a></p>"); } else { // Success: display user details (mask password) out.println("<p><strong>Full Name:</strong> " + escapeHtml(fullName) + "</p>"); out.println("<p><strong>Email:</strong> " + escapeHtml(email) + "</p>"); out.println("<p><strong>Password:</strong> " + maskPassword(password) + "</p>"); out.println("<p><strong>Gender:</strong> " + escapeHtml(gender) + "</p>"); out.println("<p><strong>Hobbies:</strong> " + (hobbies == null ? "None" : escapeHtml(Arrays.toString(hobbies))) + "</p>"); out.println("<p><strong>Country:</strong> " + escapeHtml(country) + "</p>"); // Optionally provide a link for a fresh registration out.println("<p><a href='register'>Register another user</a></p>"); } out.println("</body></html>"); } } // Basic null safe method private String safe(String s) { return s == null ? "" : s.trim(); } Advanced Java Programming 313 // Mask password when displaying private String maskPassword(String pwd) { if (pwd == null) return ""; int len = Math.min(pwd.length(), 8); return "*".repeat(len) + (pwd.length() > len ? "..." : ""); } // Simple HTML escape to avoid XSS in responses private String escapeHtml(String s) { if (s == null) return ""; return s.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\"", """) .replace("'", "'"); } } 1. The servlet is mapped to /register by the @WebServlet annotation. 2. doGet emits the registration form; doPost handles the submitted data. 3. request.setCharacterEncoding("UTF-8") ensures correct reading of Unicode form input. 4. The program masks the password when displayed and escapes HTML to reduce XSS risk. If you prefer web.xml mapping instead of annotation, use: <servlet> <servlet-name>RegisterServlet</servlet-name> <servlet-class>com.example.registration.RegisterServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RegisterServlet</servlet-name> <url-pattern>/register</url-pattern> </servlet-mapping> How to test Using browser 1. Deploy the application to Tomcat at http://localhost:8080/yourapp/. 2. Open http://localhost:8080/yourapp/register to see the form rendered by doGet. 3. Fill in the form and click Register. The doPost response page will display the submitted details. Advanced Java Programming 314 Using curl Submit form data via curl: curl -X POST \ -d "fullname=Greenfield User" \ -d "[email protected]" \ -d "password=secret123" \ -d "gender=Male" \ -d "hobby=Reading" \ -d "hobby=Gaming" \ -d "country=India" \ http://localhost:8080/yourapp/register The server will return HTML containing the registration result. Sample Outputs 1. GET request to /register (form page) Browser will show the registration form with fields Full Name, Email, Password, Gender, Hobbies and Country and a Register button. 2. POST request with valid data Page displays: Registration Result Full Name: Greenfield User Email: [email protected]om Password: ******** Gender: Male Hobbies: [Reading, Gaming] Country: India [Register another user] 3. POST request with invalid data (e.g., short password) Page displays: Registration Result Advanced Java Programming 315 Errors: Password is required and must be at least 6 characters. [Go back to form] Result The servlet that handles HTTP GET and POST methods for user registration was implemented successfully. Advanced Java Programming 316 CHAPTER – 4 Java Server Pages and JSTL 4.1 JSP Architecture and Lifecycle JavaServer Pages (JSP) architecture follows a 3-tier architecture that separates the web application into three distinct layers Presentation Layer, Logic Layer, and Data Layer. This separation of concerns ensures better maintainability, reusability, and scalability of the application. JSP allows developers to create dynamic and interactive web pages by embedding Java code directly within HTML pages. It acts as an interface between the client and the server, simplifying web development on the Java EE platform. Fig. 4.1 JSP Architecture. Three-Tier Structure of JSP Architecture 1. Presentation Layer (Client Side) The presentation layer is the front-end interface that interacts with the user. It is responsible for displaying content and collecting user input. In a JSP-based web application, this layer consists of JSP pages that combine HTML and JSP tags to produce dynamic web content. When a user makes a request (such as submitting a form or clicking a link), it is sent to the Advanced Java Programming 323 It is important to note that developers cannot override the _jspService() method because it is automatically generated and declared as final by the JSP engine. This ensures consistency in how client requests are handled. 7. JSP Cleanup Phase (jspDestroy Method) The final phase in the JSP life cycle is the cleanup or destruction phase. When the web container decides that the JSP page is no longer needed (for example, during server shutdown or when the application is undeployed), it calls the jspDestroy() method. This method is called only once before the JSP instance is removed from memory. It is used to release resources that were allocated during initialization or request processing. For example, you can close database connections, terminate background threads, or release file handles. Example: public void jspDestroy() { System.out.println("JSP Destroyed"); // Cleanup tasks such as closing database connections } Proper use of this method ensures that resources are not wasted and that the application runs efficiently even under heavy load. Important Characteristics of JSP Life Cycle 1. The JSP life cycle is fully managed by the web container, meaning that translation, compilation, loading, and destruction are automatic. 2. The jspInit() and jspDestroy() methods can be overridden by developers to perform custom initialization and cleanup tasks. 3. The _jspService() method cannot be overridden because it is generated automatically by the container. 4. The translation and compilation phases occur only the first time a JSP page is requested or when the JSP file is modified. 5. Once compiled, the JSP behaves like a servlet and follows the servlet execution model. Action Elements In JavaServer Pages (JSP), action elements are special XML-based tags that allow developers to perform dynamic operations such as including resources, forwarding requests, managing JavaBeans, and creating reusable and interactive web components. These tags are Advanced Java Programming 324 predefined in the JSP specification and are designed to perform specific tasks during the request processing phase of a JSP page. JSP action elements are written in the form of XML tags that begin with <jsp:...> and follow XML syntax rules. This makes them portable, readable, and easy to integrate with XMLbased tools and frameworks. Unlike scriptlets (which mix Java code with HTML), action elements help developers write cleaner, modular, and maintainable code by separating business logic from presentation logic. Purpose and Importance of JSP Action Elements The main purpose of using action elements is to allow the JSP container to perform predefined operations such as including pages, managing objects, and transferring control between resources dynamically, without writing explicit Java code. They enhance the functionality of JSP pages by:  Promoting code reusability  Reducing the amount of embedded Java code (scriptlets)  Improving readability and maintainability  Supporting XML-compliant syntax By using action elements, JSP pages become more modular and easier to update or extend, which is especially beneficial for large web applications. Types of JSP Action Elements The JSP specification defines several action elements, each designed to perform a particular task in the JSP execution process. The following are the most commonly used action elements. 1. <jsp:include> – Including Another Resource The <jsp:include> action element is used to dynamically include the content of another resource such as a JSP file, HTML file, or servlet into the current page during runtime. This means the inclusion happens when the page is requested, not during translation or compilation. Syntax: <jsp:include page="relativeURL" flush="true|false" />  The page attribute specifies the resource to be included.  The flush attribute, when set to true, ensures that the current output buffer is sent to the client before the inclusion occurs. Advanced Java Programming 325 Example: <jsp:include page="header.jsp" flush="true" /> This action dynamically inserts the content of header.jsp into the current page. Using <jsp:include> promotes code reuse since common components like headers, footers, or navigation bars can be maintained separately. 2. <jsp:forward> – Forwarding the Request The <jsp:forward> action element is used to forward the client request and response to another resource, such as another JSP, servlet, or HTML file. After the forwarding, control is completely transferred to the new resource, and no further output from the current page is sent to the client. Syntax: <jsp:forward page="relativeURL" /> Example: <jsp:forward page="welcome.jsp" /> In this example, when the current page executes, the request is forwarded to welcome.jsp. This is commonly used for redirecting users based on certain conditions, such as successful login or form submission. 3. <jsp:useBean> – Declaring and Using JavaBeans The <jsp:useBean> element is one of the most important action elements in JSP. It is used to instantiate or locate a JavaBean object that can be used within the page to handle data and business logic. Syntax: <jsp:useBean id="beanName" class="package.ClassName" scope="page|request|session|application" />  id specifies the name of the bean instance.  class specifies the full class name of the JavaBean.  scope defines where the bean is stored (page, request, session, or application). Example: <jsp:useBean id="user" class="com.example.UserBean" scope="session" /> Advanced Java Programming 326 If the bean instance already exists in the specified scope, it is reused; otherwise, a new instance is created. This allows JSP pages to interact with backend Java classes in a simple, structured way. 4. <jsp:setProperty> – Setting Bean Properties The <jsp:setProperty> element is used to set the value of a property in a JavaBean object. It can either assign a literal value or retrieve a value from a request parameter. Syntax: <jsp:setProperty name="beanName" property="propertyName" value="value" /> Example: <jsp:setProperty name="user" property="username" value="John" /> You can also set a property using a request parameter: <jsp:setProperty name="user" property="username" param="uname" /> This element provides a simple and declarative way to pass data from the user interface (form inputs) to the business logic represented by JavaBeans. 5. <jsp:getProperty> – Retrieving Bean Properties The <jsp:getProperty> action element is used to retrieve the value of a JavaBean property and insert it directly into the response output (HTML). Syntax: <jsp:getProperty name="beanName" property="propertyName" /> Example: <jsp:getProperty name="user" property="username" /> If the username property in the user bean has the value "John", this tag will output John on the web page. This allows dynamic display of data stored in JavaBeans without writing explicit Java code. 6. <jsp:param> – Passing Parameters The <jsp:param> element is used inside <jsp:include> or <jsp:forward> tags to pass additional parameters to the target resource. Advanced Java Programming 327 Syntax: <jsp:param name="paramName" value="paramValue" /> Example: <jsp:forward page="welcome.jsp"> <jsp:param name="user" value="John" /> </jsp:forward> Here, the parameter user with value John will be passed to welcome.jsp, allowing the target page to access the value using request.getParameter("user"). 7. <jsp:plugin> – Embedding Java Applets or Beans The <jsp:plugin> action element is used to embed Java applets or JavaBeans into a JSP page by generating browser-specific HTML tags such as <object> or <embed>. Syntax: <jsp:plugin type="applet|bean" code="classFileName" codebase="URL"> <jsp:params /> <jsp:fallback>Alternative content</jsp:fallback> </jsp:plugin> This tag ensures that the applet or bean runs correctly in browsers that support Java. The <jsp:fallback> tag provides alternate content if the plugin fails to load. 8. <jsp:fallback> – Alternative Content for Plugins The <jsp:fallback> tag is used inside a <jsp:plugin> element to define the content that should be displayed if the plugin or applet fails to load on the client browser. Example: <jsp:plugin type="applet" code="HelloApplet.class" codebase="/applets"> <jsp:fallback> Your browser does not support Java Applets. </jsp:fallback> </jsp:plugin> This ensures that users still see a message or alternate content even if the applet cannot be displayed. Advanced Java Programming 328 9. <jsp:element> – Creating Dynamic XML Elements The <jsp:element> action allows developers to dynamically create XML elements at runtime. It is often used with <jsp:attribute> and <jsp:body> to define element attributes and body content dynamically. Syntax: <jsp:element name="elementName"> <jsp:attribute name="attrName">value</jsp:attribute> <jsp:body>content</jsp:body> </jsp:element> This tag provides flexibility when generating XML or XHTML documents dynamically from JSP pages. 10. <jsp:body> and <jsp:attribute> – Defining Content Dynamically The <jsp:body> tag defines the body content of a dynamically created XML element, while <jsp:attribute> defines the attributes for that element. These tags are generally used within <jsp:element> to create dynamic structures. Example: <jsp:element name="message"> <jsp:attribute name="type">info</jsp:attribute> <jsp:body>Hello, this is a dynamic message!</jsp:body> </jsp:element> This example dynamically generates: <message type="info">Hello, this is a dynamic message!</message> Advantages of Using JSP Action Elements 1. Cleaner Code: Reduces Java code within JSP pages by providing XML-based tags. 2. Reusability: Common components like headers or beans can be reused across multiple pages. 3. Maintainability: Easier to update and debug because presentation and logic are separated. 4. XML Compatibility: Follows XML syntax, making it compatible with XML parsers and tools. 5. Dynamic Resource Management: Allows including and forwarding resources dynamically at runtime. Advanced Java Programming 329 4.2 Implicit Objects and Scripting Elements Implicit Objects In Java Server Pages, implicit objects are special predefined objects that are automatically created by the JSP container for every page request. These objects are called implicit because they are available by default to the developer without the need to declare or instantiate them manually. They can be directly accessed within scripting elements such as expressions, declarations, and scriptlets. Implicit objects simplify the interaction between the server and the client by providing easy access to important request and response information, session data, application configuration, and more. There are a total of nine implicit objects available in JSP, and each one serves a specific purpose during the execution of a JSP page. List of JSP Implicit Objects S.No Implicit Object Class Type Description 1 request HttpServletRequest Represents the client request and provides access to form data and request information. 2 response HttpServletResponse Represents the response sent to the client and allows modification of headers or redirection. 3 out JspWriter Used to send output to the client browser. 4 session HttpSession Used to store and manage user specific data during a session. 5 application ServletContext Provides application wide data and resources accessible by all JSP pages. 6 config ServletConfig Provides configuration information for the current JSP page. 7 pageContext PageContext Provides access to all JSP objects and manages different scopes. 8 page Object Refers to the current JSP page similar to the keyword this in Java. 9 exception Throwable Represents exceptions and errors thrown during JSP execution. Table. 4.1 JSP Implicit Objects. Advanced Java Programming 330 The Request Object The request object is an instance of the javax.servlet.http.HttpServletRequest class. It represents the client request made to the server. Every time a user sends a request such as submitting a form or clicking a link, the web container automatically creates a new request object. This object allows the developer to access form data, request parameters, header information, and cookies. It also provides details about the client and the server such as IP address, port, and protocol. Common Methods of request object:  getParameter(String name) – Returns the value of a form field  getHeader(String name) – Returns the value of a specific header  getCookies() – Returns an array of cookies sent by the client  getMethod() – Returns the request method such as GET or POST Example: <html> <body> <form action="welcome.jsp" method="post"> Enter Name: <input type="text" name="username"> <input type="submit" value="Submit"> </form> </body> </html> welcome.jsp Hello, <%= request.getParameter("username") %> When the user submits the form, the entered value is retrieved using the request object. The Response Object The response object is an instance of javax.servlet.http.HttpServletResponse. It represents the response that the server sends back to the client. This object allows developers to modify HTTP headers, redirect users to other pages, and add cookies to the response. Common Methods of response object:  sendRedirect(String url) – Redirects the client to another page  addCookie(Cookie cookie) – Adds a new cookie to the response Advanced Java Programming 331  setContentType(String type) – Sets the MIME type of the response  setStatus(int code) – Sets the HTTP status code Example: <% String name = request.getParameter("username"); if(name == null || name.equals("")){ response.sendRedirect("error.jsp"); }else{ out.println("Welcome " + name); } %> If the name is not entered, the user is redirected to the error page. The Out Object The out object is an instance of javax.servlet.jsp.JspWriter. It is used to send output or content to the client browser. It works similar to System.out.println() in Java but is designed specifically for web output. Common Methods of out object:  print(dataType dt) – Prints data to the client  println(dataType dt) – Prints data and moves to the next line  flush() – Flushes the output buffer Example: <% out.println("Hello from JSP!"); out.print("This page is served at: " + new java.util.Date()); %> This example displays text and the current date on the web page. The Session Object The session object is an instance of javax.servlet.http.HttpSession. It is used to store and manage user specific data across multiple requests during a single session. This means data stored in the session remains available until the session is terminated or expires. Advanced Java Programming 332 Common Methods of session object:  setAttribute(String name, Object value) – Stores data in the session  getAttribute(String name) – Retrieves data from the session  invalidate() – Destroys the session Example: <% session.setAttribute("user", "John"); out.print("Welcome, " + session.getAttribute("user")); %> This code stores the user name in the session and displays it on the page. The Application Object The application object is an instance of javax.servlet.ServletContext. It represents the overall web application and provides a way to share information among all JSP pages and servlets in the same application. This object is often used for storing global variables or initialization parameters that are accessible across the entire application. Example: <% Integer count = (Integer)application.getAttribute("visitorCount"); if(count == null){ count = 1; }else{ count++; } application.setAttribute("visitorCount", count); out.print("Total Visitors: " + count); %> This example maintains a global visitor counter shared by all users of the application. The Config Object The config object is an instance of javax.servlet.ServletConfig. It provides configuration and initialization information specific to the current JSP page. Advanced Java Programming 339 Example 1: Display a String <!DOCTYPE html> <html> <body> <%= "Welcome to JSP Expression Example" %> </body> </html> Example 2: Display a Calculation Result <!DOCTYPE html> <html> <body> <p>The sum of 8 and 12 is: <%= 8 + 12 %></p> </body> </html> Example 3: Display Current Date <%@ page import="java.util.Date" %> <!DOCTYPE html> <html> <body> <p>Current Date and Time: <%= new Date() %></p> </body> </html> Explanation: Each expression inside <%= %> is automatically evaluated and sent to the browser. It is a concise and clean way to print dynamic values without explicitly calling output methods. 4.3 Scope and EL (Expression Language) Expression Language (EL) is one of the most powerful and user-friendly features introduced in JSP 2.0. It was created to make it easier for web developers to access and manipulate data stored in JSP scopes such as request, session, application, and page without using Java code directly inside JSP files. Before the introduction of EL, developers had to use scriptlets and expression tags like <%= request.getParameter("name") %> or <%= session.getAttribute("user") %> to retrieve and display data. This approach made the code lengthy, hard to read, and difficult to maintain. Advanced Java Programming 340 EL provides a simpler, cleaner, and more readable syntax that allows developers to work with data directly using expressions inside ${}. The JSP engine automatically evaluates these expressions during runtime and inserts their values into the HTML output. For example: Without EL <%= request.getParameter("username") %> With EL ${param.username} The above EL expression retrieves the same data in a single line, without requiring any Java method calls. Why Expression Language is Used The main purpose of EL is to reduce the amount of Java code inside JSP pages and make the presentation layer more readable. It bridges the gap between HTML design and Javabased server-side logic. Key benefits include: 1. Simplified data access: You can directly access data from request, session, application, and page scopes without using Java code. 2. Improved readability: Pages look more like HTML templates and less like Java programs. 3. Less maintenance: With fewer Java statements in JSP pages, debugging and maintaining the code becomes easier. 4. Type conversion: EL automatically converts values (such as Strings, numbers, and Booleans) without the need for explicit casting. 5. Powerful operations: Supports arithmetic, logical, relational, and conditional operations. 6. Integration with JSTL: EL works seamlessly with JSTL (JSP Standard Tag Library) for looping, conditional checks, and displaying data. Syntax of Expression Language The basic syntax of EL is simple and uniform across all JSP pages. Every EL expression is enclosed between ${ and }. Syntax: ${expression} Advanced Java Programming 341 The JSP container evaluates the expression at runtime and automatically replaces it with its corresponding value in the final HTML output. Example: <h3>Welcome, ${param.username}</h3> If the user entered “Arjun” in a form field named username, then the output will be: Welcome, Arjun Working Principle of Expression Language When a JSP page containing EL is executed, the JSP engine performs the following steps: 1. It scans the page and identifies expressions enclosed within ${}. 2. It evaluates these expressions at runtime. 3. It searches for the variable in the following scopes in this order:  Page Scope  Request Scope  Session Scope  Application Scope 4. Once the variable is found, its value is returned and automatically converted into a string. 5. The evaluated value is inserted into the HTML response sent to the client browser. If the variable is not found in any of the scopes, EL simply returns null instead of throwing an exception. Common Uses of Expression Language EL can perform a variety of tasks in JSP pages. Below are some of its most common applications. 1. Accessing Variables You can directly access variables or attributes without using Java method calls. ${name} ${sessionScope.userName} ${applicationScope.totalVisitors} 2. Accessing Object Properties EL can access properties of JavaBeans or custom objects using the dot operator. Advanced Java Programming 342 ${student.name} ${student.rollNumber} ${employee.salary} If student or employee is an object available in one of the scopes, EL will automatically fetch and display the property value. 3. Accessing Request Parameters EL can easily access form parameters sent by the client. ${param.email} ${param.password} If a form field with the name email is submitted, EL automatically retrieves its value. 4. Arithmetic Operations EL supports mathematical calculations directly inside the expression. ${10 + 5} ${price * quantity} ${100 / 4} ${total % 2} Example: <% request.setAttribute("price", 250); request.setAttribute("quantity", 3); %> <p>Total Cost: ₹${price * quantity}</p> Output: Total Cost: ₹750 5. Logical and Relational Operations EL supports comparison and logical operators. ${age >= 18} ${marks < 40 || result == "fail"} ${salary != 0} Advanced Java Programming 343 These expressions return either true or false. 6. Conditional Expressions EL allows conditional decision-making using the ternary operator. ${age >= 18 ? "You can vote" : "You are too young to vote"} 7. Accessing Arrays and Collections EL allows accessing elements of arrays, lists, or maps easily. ${fruits[0]} ${students[2].name} ${map.key} Practical Examples of Expression Language Example 1: Accessing Request Attributes <% request.setAttribute("city", "Bangalore"); %> <h3>Your city is: ${requestScope.city}</h3> Output: Your city is: Bangalore Example 2: Accessing Session Attributes <% session.setAttribute("user", "Priya"); %> <p>Logged in as: ${sessionScope.user}</p> Output: Logged in as: Priya Example 3: Accessing Application Attributes <% application.setAttribute("visitors", 1234); %> Advanced Java Programming 344 <p>Total Visitors: ${applicationScope.visitors}</p> Output: Total Visitors: 1234 Example 4: Conditional Expression <% request.setAttribute("age", 16); %> <h3>${age >= 18 ? "Eligible for voting" : "Not eligible for voting"}</h3> Output: Not eligible for voting Example 5: Using EL in a Form form.jsp <form action="welcome.jsp" method="post"> Enter Name: <input type="text" name="username"><br> <input type="submit" value="Submit"> </form> welcome.jsp <p>Welcome, ${param.username}!</p> If the user enters “Ravi” in the form, the output will be: Welcome, Ravi! Example 6: Using EL with JSTL for Iteration <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% List<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Mango"); fruits.add("Orange"); request.setAttribute("fruitList", fruits); %> Advanced Java Programming 345 <table border="1"> <tr><th>Fruit Name</th></tr> <c:forEach var="fruit" items="${fruitList}"> <tr><td>${fruit}</td></tr> </c:forEach> </table> Output: Fruit Name Apple Mango Orange Operators in Expression Language EL supports several categories of operators, similar to Java. 1. Arithmetic Operators +, -, *, /, % Example: ${10 + 5} → 15 ${20 % 3} → 2 2. Relational Operators ==, !=, <, >, <=, >= Example: ${marks >= 35} ${num1 == num2} 3. Logical Operators &&, ||, ! Example: ${age > 18 && country == "India"} Advanced Java Programming 346 4. Conditional Operator condition ? value1 : value2 Example: ${gender == "M" ? "Male" : "Female"} 5. Empty Operator Checks if a value or collection is empty or null. Example: ${empty name} ${empty studentList} Implicit Objects in Expression Language EL provides implicit objects that automatically give access to commonly used data sources such as request parameters, session data, and application attributes. Implicit Object Description pageScope Refers to attributes available only within the current JSP page. requestScope Refers to attributes for the current client request. sessionScope Refers to attributes stored for the user session. applicationScope Refers to attributes shared across the entire web application. param Provides access to request parameters. paramValues Provides access to request parameters as arrays. header Provides access to HTTP request headers. cookie Provides access to cookies sent by the client. initParam Accesses initialization parameters from web.xml. pageContext Provides access to JSP-specific information like request, response, and session. Table. 4.2 Implicit Objects in Expression Language Advanced Java Programming 347 Scopes in Expression Language Expression Language works closely with JSP scopes to manage data across pages and requests. There are four main scopes in EL. 1. Page Scope  Attributes stored here are accessible only within the current JSP page.  The data is lost once the page response is sent to the client.  Accessed using ${pageScope.variable}. 2. Request Scope  Attributes are available for a single client request and can be shared across servlets or JSPs using forward or include.  Accessed using ${requestScope.variable}. 3. Session Scope  Attributes are maintained throughout the user session.  Data remains available across multiple pages until the session ends.  Accessed using ${sessionScope.variable}. 4. Application Scope  Attributes are available to all servlets and JSP pages in the application.  Data remains until the server stops or the application is undeployed.  Accessed using ${applicationScope.variable}. Example Demonstrating All Scopes <% pageContext.setAttribute("data", "Page Data"); request.setAttribute("data", "Request Data"); session.setAttribute("data", "Session Data"); application.setAttribute("data", "Application Data"); %> <p>Page Scope: ${pageScope.data}</p> <p>Request Scope: ${requestScope.data}</p> <p>Session Scope: ${sessionScope.data}</p> <p>Application Scope: ${applicationScope.data}</p> Advanced Java Programming 348 Output: Page Scope: Page Data Request Scope: Request Data Session Scope: Session Data Application Scope: Application Data Reserved Words in Expression Language EL includes several reserved keywords that cannot be used as variable names. These include: true, false, null, and, or, not, eq, ne, lt, le, gt, ge, div, mod, instanceof, and empty. Comparison Between JSP Scriptlets and Expression Language Operation Using JSP Scriptlets Using Expression Language Retrieve request parameter <%= request.getParameter("name") %> ${param.name} Retrieve session attribute <%= session.getAttribute("user") %> ${sessionScope.user} Retrieve application attribute <%= application.getAttribute("count") %> ${applicationScope.count} Conditional check <%= (age >= 18) ? "Adult" : "Minor" %> ${age >= 18 ? "Adult" : "Minor"} Access bean property <%= user.getEmail() %> ${user.email} Table. 4.3 Comparison Between JSP Scriptlets and Expression Language. 4.4 JSP Standard Tag Libraries (JSTL) JSTL stands for JavaServer Pages Standard Tag Library. It is a set of standardized tag libraries that encapsulate many common tasks that JSP developers used to implement with scriptlets and custom tags. The goals are  make JSP pages more declarative and readable  reduce and ideally eliminate Java scriptlets in view pages Advanced Java Programming 355  SQL tags are vulnerable to misuse. Use prepared statements and parameter binding always via <sql:param>  Do not store credentials in JSP files. Use JNDI DataSource configured in container for production  Prefer frameworks like JPA, MyBatis or JDBC DAOs for real applications Transaction support sql:transaction groups operations and commits or rollbacks together. Still not a substitute for proper container managed transactions. XML tag library x with examples XML JSTL is useful when you need light weight XML parsing, XPath queries and XSL transforms directly in the view layer. x:parse Parse XML from body or url into a variable <x:parse var="doc"> <root><item id="1">A</item><item id="2">B</item></root> </x:parse> Or parse a remote XML <x:parse var="doc" xml="${param.xmlUrl}" /> x:out and XPath Use XPath expressions to extract nodes or values <x:out select="$doc/root/item[1]" /> x:forEach select Iterate node sets returned by XPath <x:forEach select="$doc/root/item" var="it"> <div><x:out select="$it" /></div> </x:forEach> Advanced Java Programming 356 x:transform Apply XSLT transformation to an XML node with optional parameters <x:transform xml="${doc}" xslt="/WEB-INF/xslt/report.xsl"> <x:param name="title" value="Monthly Report" /> </x:transform> Practical tips for XML JSTL  Ensure your container has the XML/XSLT implementation jars if not included  For large XML documents, parsing in the controller and supplying parsed object to JSP may be more efficient  Use XPath carefully and test expressions in isolation Functions library fn explained Functions are static utilities available for use inside EL. They return values and can be composed. Common functions and usage patterns ${fn:length(list)} // size of list or length of string ${fn:contains(name, 'Raj')} // boolean ${fn:toLowerCase(username)} ${fn:toUpperCase(title)} ${fn:split(tags, ',')[0]} ${fn:replace(text, 'old', 'new')} ${fn:trim(name)} Functions are especially helpful in views to avoid having to create small helper objects or scriptlets. Integration with Expression Language  JSTL tags are primarily designed to be used with EL expressions for values and tests  The recommended approach is to do data setup in servlets or controllers and use JSTL tags for presentation  Combine tags and EL to minimize Java code in JSP Example combined usage <c:set var="taxRate" value="${applicationScope.taxConfig.defaultRate}" /> Advanced Java Programming 357 <c:forEach var="p" items="${products}"> <div>${p.name} - Total: ${p.price + (p.price * taxRate)}</div> </c:forEach> Security considerations  Cross site scripting: Always escape untrusted user input. Use <c:out value="${...}" /> which escapes HTML by default. If you must render HTML, ensure the content is sanitized before printing and set escapeXml="false" only with caution.  SQL injection: Never concatenate user input into SQL. Use <sql:param> to pass values. For production use parameterized queries in DAOs.  Remote import: c:import and x:parse with remote URLs can introduce SSRF or availability issues. Validate or restrict remote endpoints.  Sensitive data: Do not expose credentials, secrets or internal information in JSP. Use container level JNDI resources. Performance and scalability tips  Avoid heavy computation or database access in JSP. Prepare data in controllers.  Use scoped attributes judiciously. Session and application scopes are memory consumers.  Avoid repeatedly parsing or transforming the same XML in multiple requests. Cache results in application scope if safe.  Use container pooling and JNDI DataSource for DB connections rather than sql:setDataSource.  Minimize nested loops in JSP and expensive EL expressions inside loops.  Consider using jsp:include versus c:import depending on whether you want translation time versus request time inclusion. Developing Dynamic Web Applications with JSP Developing dynamic web applications with JavaServer Pages (JSP) involves creating interactive, data-driven websites that respond intelligently to user requests. JSP is a serverside technology that allows developers to combine static content, such as HTML, CSS, and JavaScript, with dynamic elements generated using Java. It is part of the Java EE (Enterprise Edition) platform and provides an efficient way to build robust, scalable, and maintainable web applications. JSP in Web Development In traditional static websites, every page is fixed the same content is displayed for all users. However, most modern web applications require dynamic behavior, such as displaying personalized user information, processing forms, connecting to databases, and generating Advanced Java Programming 358 content based on conditions or inputs. JSP was introduced to simplify this process by embedding Java code directly into HTML pages, allowing developers to generate dynamic content on the server before sending it to the client’s browser. When a user requests a JSP page, the web server forwards the request to a JSP engine. The JSP engine converts the page into a servlet, a Java class that runs on the server. The servlet processes the request, executes Java code (such as retrieving data from a database), and generates HTML as a response. This response is then sent back to the client’s browser for display. How JSP Supports Dynamic Content JSP makes it possible to develop dynamic web applications by providing a seamless way to mix presentation logic (HTML) with server-side logic (Java). Through Expression Language (EL) and JSP Standard Tag Library (JSTL), developers can write clean, readable pages that separate design and functionality. Instead of writing complex Java code directly inside JSP, EL expressions such as ${user.name} can be used to access server-side data easily. Similarly, JSTL tags like <c:forEach> or <c:if> help control iteration and conditional rendering without using Java scriptlets. For example: <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <h3>Welcome, ${sessionScope.username}</h3> <c:if test="${not empty sessionScope.username}"> <p>Your account balance is: ${sessionScope.balance}</p> </c:if> This snippet dynamically displays user data stored in the session. The content changes depending on the user who is logged in this is the essence of a dynamic web page. JSP in the MVC Architecture In modern Java web applications, JSP is often used as the view layer in the Model-ViewController (MVC) architecture.  The Model represents the business logic and data (for example, JavaBeans or database records).  The Controller (usually a servlet) handles client requests, processes business logic, and determines which JSP page to display.  The View (JSP) is responsible for presenting data to the user in a readable format. This separation of concerns improves maintainability and reusability. For example, if you need to change how data is displayed, you only modify the JSP file without touching the business logic. Advanced Java Programming 359 Working with Databases Using JSP Dynamic web applications typically interact with databases to store and retrieve data. JSP can easily connect to databases using JDBC (Java Database Connectivity) or through frameworks and tag libraries such as JSTL SQL tags for quick prototyping. For instance, you can use JSP to fetch and display user data stored in a MySQL database. A servlet may retrieve data from the database and set it as an attribute: request.setAttribute("products", productList); request.getRequestDispatcher("/products.jsp").forward(request, response); Then, in the JSP page, you can iterate over that data: <c:forEach var="p" items="${products}"> <p>${p.name} - Price: ${p.price}</p> </c:forEach> This demonstrates how JSP and servlets work together to generate content dynamically based on backend data. Advantages of Using JSP for Dynamic Applications JSP provides several advantages when developing dynamic web applications. It simplifies web development by allowing Java code and HTML to coexist seamlessly. It supports rapid development through reusable components such as JavaBeans, custom tags, and JSTL, which promote modular design. JSP pages are compiled into servlets automatically by the server, which ensures high performance and portability across platforms. It also integrates well with other Java technologies like Servlets, JDBC, and Enterprise JavaBeans (EJB), making it suitable for both small websites and large enterprise applications. Another major advantage is the separation of presentation and business logic. Designers can focus on HTML and user interface, while developers handle logic through servlets and JavaBeans. This separation makes collaboration easier and the application more maintainable. Example: Dynamic Login Page Using JSP and Servlet Below is a simple example showing how JSP and Servlets work together to create a dynamic login feature: login.jsp <form action="LoginServlet" method="post"> Username: <input type="text" name="username"><br> Password: <input type="password" name="password"><br> Advanced Java Programming 360 <input type="submit" value="Login"> </form> LoginServlet.java @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String user = request.getParameter("username"); String pass = request.getParameter("password"); if("admin".equals(user) && "1234".equals(pass)) { request.setAttribute("username", user); request.getRequestDispatcher("welcome.jsp").forward(request, response); } else { response.sendRedirect("error.jsp"); } } } welcome.jsp <h2>Welcome, ${requestScope.username}!</h2> <p>You have successfully logged in.</p> This simple example illustrates how a JSP page interacts with a servlet to generate dynamic responses based on user input. Key Features Supporting Dynamic Development 1. Expression Language (EL): Simplifies accessing data from JavaBeans, request parameters, and scope objects. 2. JSTL (JSP Standard Tag Library): Provides standard tags for loops, conditions, database access, and internationalization. 3. JavaBeans Integration: Allows encapsulating data and logic in reusable components that can be accessed through JSP pages. 4. Custom Tags: Extend JSP functionality by defining reusable tag sets for specific application needs. 5. Implicit Objects: Predefined objects like request, response, session, and application provide access to the request-response cycle and stored data. Advanced Java Programming 361 Practical 1. Design a responsive UI form using Bootstrap and React to capture user input and display the result Aim To design and implement a responsive user input form using Bootstrap and React.js that captures user data (like name, email, and message) and dynamically displays the entered information on the same page. Procedure 1. Create a React project: Open the terminal and create a new React app: 2. npx create-react-app responsive-form 3. cd responsive-form 4. Install Bootstrap: Install Bootstrap for styling and responsiveness: 5. npm install bootstrap Import Bootstrap CSS in the index.js or App.js file: import 'bootstrap/dist/css/bootstrap.min.css'; 6. Design a responsive form: Use Bootstrap grid and form classes to create a responsive form layout with input fields (Name, Email, Message) and a Submit button. 7. Capture user input: Use React’s useState hook to manage form data and update state as the user types. 8. Display the result: After submission, display the entered data dynamically below the form using React components. 9. Run and test the project: Start the development server using: 10. npm start Test the form in a browser and check the responsiveness by resizing the window. Program (React Code) File: App.js import React, { useState } from "react"; import 'bootstrap/dist/css/bootstrap.min.css'; function App() { // State variables for form fields const [formData, setFormData] = useState({ Advanced Java Programming 362 name: "", email: "", message: "" }); const [submittedData, setSubmittedData] = useState(null); // Handle input change const handleChange = (e) => { const { name, value } = e.target; setFormData({ ...formData, [name]: value }); }; // Handle form submission const handleSubmit = (e) => { e.preventDefault(); setSubmittedData(formData); }; return ( <div className="container mt-5"> <div className="card shadow-lg p-4 rounded-4"> <h2 className="text-center text-primary mb-4">User Information Form</h2> <form onSubmit={handleSubmit}> <div className="mb-3"> <label className="form-label fw-bold">Name:</label> <input type="text" className="form-control" name="name" placeholder="Your full name" value={formData.name} onChange={handleChange} required /> </div> <div className="mb-3"> <label className="form-label fw-bold">Email:</label> <input type="email" className="form-control" name="email" placeholder="you@example" value={formData.email} Advanced Java Programming 363 onChange={handleChange} required /> </div> <div className="mb-3"> <label className="form-label fw-bold">Message:</label> <textarea className="form-control" name="message" rows="3" placeholder="Write your message here" value={formData.message} onChange={handleChange} required ></textarea> </div> <button type="Submit" className="btn btn-primary w-100"> Submit </button> </form> </div> {submittedData && ( <div className="card mt-5 p-4 border-success border-2 rounded-4"> <h4 className="text-success">Submitted Information</h4> <hr /> <p><strong>Name:</strong> {submittedData.name}</p> <p><strong>Email:</strong> {submittedData.email}</p> <p><strong>Message:</strong> {submittedData.message}</p> </div> )} </div> ); } export default App; Explanation 1. React Hooks (useState) are used to manage form state dynamically. 2. Bootstrap classes (container, form-control, card, btn, text-center) ensure the layout is responsive and visually appealing. Advanced Java Programming 364 3. When the form is submitted, the input data is stored in a state variable (submittedData) and displayed immediately below the form. 4. The form is mobile-friendly, with all elements automatically resizing and aligning properly on smaller screens. Sample Output Before Submission A clean, centered form appears: After Submission Once the user clicks “Submit”, the entered data appears dynamically below: Submitted Information ---------------------- Name: Priya Sharma Email: [email protected] Advanced Java Programming 371 The View The View is the presentation layer. Its job is to render the model into a human readable form and present UI controls for user interaction. In server rendered web apps the view is typically HTML templates or JSP pages. In single page applications views are React or Angular components. A key point is that the view should contain minimal business logic; it should be concerned with presentation, formatting, and calling controller actions or emitting UI events. Example view responsibilities  Rendering lists of items and detail pages  Formatting numbers and dates for locale  Binding UI inputs to controller actions or events Small EJS view snippet (Node): <ul> <% bookings.forEach(function(b) { %> <li><%= b.user %> booked <%= b.hotel %> on <%= b.date.toDateString() %></li> <% }) %> </ul> The Controller The Controller is the coordinator. It receives input events (HTTP requests, button clicks), extracts user intent, invokes the appropriate model operations, and selects the view for response. Controllers handle routing, map request parameters to model calls, orchestrate transactions, and decide on redirects, errors, or rendering templates. Example responsibilities  Parsing and validating request parameters  Calling model methods and services  Choosing which view to return and what data to pass to it Simple Express controller (Node): // controllers/bookingController.js exports.createBooking = async (req, res) => { const { user, hotel, date, price } = req.body; try { await bookingService.create({ user, hotel, date, price }); res.redirect('/bookings'); } catch (err) { res.status(500).render('error', { error: err }); Advanced Java Programming 372 } }; Typical request flow A common flow in an MVC web app follows these steps: 1. Browser sends a request to a URL. 2. Router maps the URL to a controller action. 3. Controller reads inputs, calls model / service. 4. Model interacts with the database and returns results. 5. Controller chooses a view and provides model data. 6. View renders HTML (or JSON) and it is returned to the client. This flow makes debugging easier because you can inspect each stage independently. Implementation examples across stacks 1. Server side rendering with Java and Spring MVC Spring MVC maps URLs to controller methods, returns a view name and adds model attributes: @Controller public class BookingController { @GetMapping("/bookings") public String list(Model model) { List<Booking> bookings = bookingService.findAll(); model.addAttribute("bookings", bookings); return "bookings"; // renders bookings.jsp or bookings.html } } Model and repository are separate. Views can be JSP, Thymeleaf, or other template engines. 2. Node.js with Express and EJS (example in your prompt) Express routers map to controller functions which render EJS views using data from Mongoose models: router.get('/bookings', bookingController.getBookings); Model is Booking Mongoose schema, controller fetches bookings and calls res.render('bookings', { bookings }). Advanced Java Programming 373 3. Django (MVT variant) Django uses Model View Template naming but maps similarly: view functions act like controllers, templates are views and models are ORM classes. Example: # views.py def bookings(request): bookings = Booking.objects.all() return render(request, 'bookings.html', {'bookings': bookings}) 4. Single page applications with React and Redux SPA architecture often maps MVC concepts differently: React components are views, actions and reducers (Redux) act as controllers and models respectively, or the model is the backend API. Example flow:  UI dispatches action -> async thunk calls API -> API returns data -> reducer stores data in state -> view re-renders with new model state. Asynchronous programming in MVC Modern web apps need to handle asynchronous operations. MVC supports async by:  Making model calls asynchronous (non blocking database or API calls).  Controllers using async/await or callback mechanisms to call models and respond when results arrive.  Views rendered client side updated via AJAX or fetch without full page reload. Example Node controller with async: exports.getBookings = async (req, res) => { const bookings = await Booking.find().exec(); res.render('bookings', { bookings }); }; On the client an AJAX call can fetch JSON and update a view component. Advantages of MVC summarized  Separation of concerns makes codebase clearer.  Parallel development allows front end and back end teams to work independently.  Reusability of models across multiple views or controllers.  Testability since controllers and models can be unit tested independent of views.  Maintainability and scalability as features grow. Advanced Java Programming 374 Common challenges and pitfalls  Over engineering for small apps MVC can add unnecessary layers for trivial projects.  Improper separation where views contain business logic or models directly render HTML defeats the purpose.  Too many responsibilities in controllers leading to fat controllers. Controllers should orchestrate but not implement heavy business logic. That belongs in services or model layer.  Complex cross cutting concerns like authentication and logging should be handled by middleware or filters rather than controllers. 5.2 Spring Framework The Spring Framework is a lightweight and powerful Java framework widely used for developing scalable and maintainable enterprise-level applications. It provides a wellstructured programming and configuration model for Java-based development. Spring simplifies the complexity of building enterprise software by providing an organized architecture that encourages good practices such as modularity, flexibility, and loose coupling between different components. Evolution of the Spring Framework The Spring Framework was first introduced in June 2003 under the Apache 2.0 license. Over the years, it has evolved through several major updates and improvements. Spring 2.0 introduced XML namespaces and support for AspectJ. Later, Spring 2.5 brought annotationbased configuration, allowing developers to configure applications more easily using annotations instead of XML files. Spring 3.0 introduced the Java-based configuration model using the @Configuration annotation, while Spring 4.0 added support for Java EE 7 features such as JMS 2.0, JPA 2.1, Bean Validation 1.1, Servlet 3.1, and JCache. Spring 5.0 introduced support for reactive programming and required Java 8 or later. As of 2025, the latest version is Spring Framework 6, which supports Java 17 and above, Jakarta EE 10, and native compilation using GraalVM. Although older Java versions can still be used, Java SE 6 remains the minimum requirement. Benefits of Using the Spring Framework Spring offers several advantages that make it a preferred choice for Java developers. The first major benefit is simplified development. By using concepts such as dependency injection and aspect-oriented programming, Spring helps developers reduce repetitive and boilerplate code, making the process faster and more efficient. Advanced Java Programming 375 Another important benefit of Spring is its ability to promote loose coupling between different parts of an application. With dependency injection, classes do not directly depend on one another. Instead, dependencies are managed by the Spring container, which improves maintainability and makes the code easier to test. Spring also provides a modular architecture that allows developers to include only the components they need. This makes applications lighter, more efficient, and easier to customize. Integration is another strong feature of Spring. It provides built-in support for technologies like JDBC, JMS, and JPA, enabling developers to easily connect with databases and other enterprise systems. Finally, the framework is highly scalable. Its flexible and lightweight design supports the creation of applications that can grow from small systems to large-scale enterprise platforms without compromising performance. Key Features of the Spring Framework Dependency Injection Dependency Injection is one of the most significant features of the Spring Framework. It is a design pattern where the Spring container provides the required dependencies to a class automatically. This approach eliminates the need for a class to create its own dependencies, leading to more modular and testable code. For example, consider a Library class that depends on a Book class. Instead of the Library creating a Book object directly, Spring injects it from the outside. // Constructor Injection public class Library { private Book book; public Library(Book book) { this.book = book; } } Spring also supports setter-based injection, where dependencies are provided through setter methods after the object is created. Advanced Java Programming 376 // Setter Injection public class Library { private Book book; public void setBook(Book book) { this.book = book; } } Lastly, field injection allows Spring to inject dependencies directly into fields using annotations such as @Autowired. // Field Injection public class Library { @Autowired private Book book; } Aspect-Oriented Programming (AOP) Aspect-Oriented Programming in Spring allows developers to separate cross-cutting concerns like logging, security, and transaction management from the core business logic. This helps make the application code cleaner and easier to maintain. For instance, logging actions can be implemented separately as an aspect and automatically applied across multiple classes without rewriting the same code everywhere. Transaction Management Spring provides a consistent and reliable abstraction layer for managing transactions. It ensures that operations on databases or messaging systems are completed correctly and safely. Whether the underlying technology is JDBC or JPA, Spring handles the transaction logic uniformly. Spring MVC Spring MVC is a powerful framework that follows the Model View Controller pattern for web application development. It separates the business logic, presentation layer, and control flow, allowing each part to be managed independently. The DispatcherServlet in Spring MVC acts as the front controller, routing requests to appropriate controllers and returning responses to the user interface. Advanced Java Programming 377 Fig. 5.2 Spring Framework Architecture. Spring Security Spring Security is a specialized module that provides a comprehensive security solution for enterprise applications. It manages authentication, authorization, and protection against vulnerabilities such as cross-site scripting and SQL injection. For example, you can easily restrict access to certain pages of a web application based on user roles with minimal configuration. Spring Data Spring Data simplifies the process of interacting with databases. It provides abstractions for working with both relational and non-relational databases, eliminating the need for writing repetitive data access code. For instance, developers can use interfaces like CrudRepository or JpaRepository to perform operations such as saving, deleting, or finding data without manually writing SQL queries. Spring Batch Spring Batch is designed for handling large-scale data processing tasks. It can efficiently read, process, and write bulk data such as financial transactions or inventory records. It is often used for automating scheduled jobs such as monthly report generation or daily data synchronization. Advanced Java Programming 378 Integration with Other Frameworks Spring integrates seamlessly with several other frameworks such as Hibernate, JPA, JMS, and Struts. This allows developers to build versatile applications by combining the strengths of multiple technologies within the same project. Concepts of Spring Framework Dependency Injection Dependency Injection in Spring is a design technique used to achieve loose coupling and flexibility. It allows external sources to provide dependencies to a class instead of the class creating them internally. This makes the code more adaptable and easier to modify when requirements change. There are three main types of dependency injection: constructor injection, setter injection, and field injection. Constructor injection provides dependencies when the object is created, setter injection provides them through a setter method, and field injection uses annotations to inject dependencies directly into class fields. Inversion of Control (IoC) Container Inversion of Control, also known as IoC, is a principle that delegates the responsibility of creating and managing objects to a container or framework rather than the application itself. In Spring, this responsibility is handled by the IoC container, which manages object creation and their dependencies. Spring provides two main types of IoC containers: BeanFactory and ApplicationContext. BeanFactory is the simpler container that initializes beans only when needed. It is suitable for small or lightweight applications. For example: Resource resource = new ClassPathResource("beans.xml"); BeanFactory factory = new XmlBeanFactory(resource); Book book = (Book) factory.getBean("book"); In this example, the BeanFactory creates the Book bean only when it is requested. The ApplicationContext is a more advanced container that provides extra features such as event propagation, internationalization, and support for AOP. It is the preferred option in most enterprise applications. Advanced Java Programming 379 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Book book = (Book) context.getBean("book"); Spring Annotations Spring Annotations provide metadata that defines how the framework should manage components and their dependencies. They allow configuration directly within the Java code, removing the need for extensive XML configuration. Some commonly used annotations are @Component, which marks a class as a Springmanaged bean; @Autowired, which automatically injects dependencies into a class; @Bean, which explicitly defines a bean in a configuration class; and @Configuration, which specifies that a class contains bean definitions. These annotations make the configuration process easier and more readable. Spring Modules  The Spring Framework is divided into several modules that serve different purposes in application development. The Spring Core Module provides the basic functionality of the framework and includes the IoC container for managing beans and dependencies.  The Spring AOP Module handles aspect-oriented programming, enabling developers to define reusable aspects for logging, transaction management, and performance monitoring.  The Spring ORM Module facilitates database interactions by integrating with ORM frameworks such as Hibernate and JDO. It simplifies data access and transaction handling.  The Spring Web MVC Module implements the model view controller architecture for building web-based applications. It manages user requests and responses through the DispatcherServlet and controller components.  The Spring DAO Module provides a layer for accessing data through JDBC or other technologies while simplifying transaction management. The Spring Application Context Module extends the Core Module and provides additional features like validation, internationalization, event propagation, and resource loading.  Finally, the Spring Web Flow Module helps in defining and managing the flow of user interactions across multiple pages or steps in a web application. Advanced Java Programming 380 5.3 Spring MVC and Spring Boot Spring MVC Spring MVC implements the Model View Controller pattern for building web applications. The framework centers on a front controller named DispatcherServlet that receives every HTTP request, determines which controller should handle it, invokes that controller, and then selects and renders the appropriate view. In Spring MVC you mark controller classes with the @Controller annotation and you map incoming web requests to specific handler methods using @RequestMapping or the newer composed request mapping annotations. Model view The Model encapsulates the application data and generally consists of plain old Java objects. The View is responsible for rendering model data into a format the client can understand, typically HTML. The Controller handles incoming requests, invokes business logic or services, builds the model, and returns the logical name of the view to render. DispatcherServlet and its responsibilities DispatcherServlet is the core component in Spring MVC. It acts as the front controller for all HTTP requests. After receiving a request it finds the appropriate controller by consulting handler mappings. It then forwards the request to that controller. After the controller executes it returns a ModelAndView or a view name and model data. DispatcherServlet uses a view resolver to locate the actual view implementation and then passes the model data to the view for rendering. Typical request processing flow When a request arrives DispatcherServlet intercepts it and consults the configured handler mapping to identify the handler controller. The selected controller processes the request, typically by calling service layer methods and populating model attributes. The controller then returns a ModelAndView object or a logical view name. DispatcherServlet asks the view resolver to translate the logical view name into a concrete view, and the resolved view renders the model data into the HTTP response that is sent back to the client. Spring MVC Flow In the Spring MVC framework, the DispatcherServlet plays the central role in handling all incoming HTTP requests. When a request is received, the DispatcherServlet first intercepts it and consults the configured HandlerMapping to determine which controller should process the request. Once the appropriate controller is identified, the DispatcherServlet forwards the request to that controller. Advanced Java Programming 387 <h2>${message}</h2> </body> </html> The expression ${message} refers to the model attribute added by the controller. Required configuration details and customization When DispatcherServlet is initialized it looks for a file named [servlet-name]-servlet.xml in WEB-INF by default. If you prefer a different location or name you can register a ContextLoaderListener and set the contextConfigLocation parameter in web.xml. The servlet mapping determines which URL patterns DispatcherServlet will process. For example mapping *.jsp will route requests ending with .jsp through that servlet. Advantages of Spring MVC Spring Web MVC enables clean separation of concerns by splitting input logic, business logic, and view logic. The framework supports rapid parallel development because designers and developers can work independently on views and controllers. The layered structure also makes debugging easier and it facilitates maintainability and updates in large projects. Spring Boot Spring Boot is an extension of the Spring Framework that simplifies the process of developing stand-alone, production-ready applications. It was introduced by the Spring team to eliminate the need for extensive configuration and boilerplate code that traditional Spring applications often required. With Spring Boot, developers can quickly set up and run applications with minimal setup, focusing more on the business logic rather than infrastructure and environment configuration. The main goal of Spring Boot is to make Spring-based development easier, faster, and more efficient. It follows a “convention over configuration” approach, which means most configurations are automatically handled based on project dependencies and structure. This significantly reduces manual setup and allows developers to start developing applications almost immediately. Need for Spring Boot Before Spring Boot, creating a Spring application required defining multiple XML configuration files, manually setting up dependency injection, configuring view resolvers, and managing servlet containers. This process was time-consuming and complex, especially Advanced Java Programming 388 for beginners. Developers had to configure frameworks like Hibernate, JPA, and security manually. Spring Boot solved this problem by providing a pre-configured environment with sensible defaults. It automatically sets up the application based on the dependencies present in the classpath. For example, if Spring Boot detects a web dependency, it automatically configures an embedded server like Tomcat or Jetty, sets up a default dispatcher servlet, and prepares the application to handle web requests. Core Features of Spring Boot 1. Auto Configuration One of the most important features of Spring Boot is auto configuration. It automatically configures Spring applications based on the libraries and dependencies available in the classpath. For instance, if you include a database dependency, Spring Boot automatically sets up a DataSource and a connection pool without requiring any manual configuration. However, developers can override these default configurations if needed, providing flexibility and control. 2. Stand-Alone Applications Spring Boot applications are stand-alone and self-contained. Unlike traditional Spring applications that require an external application server, Spring Boot includes embedded servers such as Tomcat, Jetty, or Undertow. This means developers can run their applications directly as Java programs without deploying them separately to a server. This feature simplifies the deployment process and makes testing and development faster. 3. Spring Boot Starters Spring Boot provides a set of pre-defined starter dependencies known as “Spring Boot Starters.” These starters are convenient dependency descriptors that allow developers to include a set of commonly used dependencies for specific functionalities. For example, spring-boot-starter-web includes everything needed to build a web application, including Spring MVC, Tomcat, and JSON converters. This feature saves time and prevents version conflicts between different dependencies. 4. Spring Boot CLI (Command Line Interface) Spring Boot comes with a Command Line Interface (CLI) that allows developers to run and test Spring applications quickly. It supports writing simple applications using Groovy scripts. This feature is particularly useful for prototyping, scripting, and testing ideas rapidly without setting up a complete Java project. Advanced Java Programming 389 5. Spring Boot Actuator Spring Boot Actuator provides built-in tools for monitoring and managing applications in production. It offers various endpoints that expose metrics such as application health, performance, and environment details. Actuator makes it easier to integrate with monitoring systems and manage the lifecycle of the application effectively. For example, endpoints like /actuator/health or /actuator/metrics provide real-time insights into the application’s performance and state. 6. Spring Boot DevTools Spring Boot DevTools enhances the developer experience by providing automatic restarts, live reloads, and improved productivity features. Whenever a developer makes changes in the code, the application restarts automatically, reflecting the latest updates without requiring a manual restart. This significantly reduces development time and improves efficiency. Spring Boot Architecture The architecture of Spring Boot is designed to simplify the traditional Spring framework. It is built on top of the Spring Framework and follows a layered architecture that includes several essential components. At the core lies the Spring Framework, which provides dependency injection, AOP, and data access support. On top of this, Spring Boot adds auto configuration, which eliminates the need for manual XML configuration. The Spring Boot Starters act as dependency managers, automatically including all required libraries for specific functionalities. The Embedded Server Layer allows applications to run independently without deploying to external servers. The Actuator Layer handles monitoring, metrics, and management tasks, while the Spring Boot CLI Layer supports fast testing and execution of applications. How Spring Boot Works When a Spring Boot application starts, it follows a specific startup process. The main class of the application is annotated with @SpringBootApplication, which is a combination of three important annotations @EnableAutoConfiguration, @ComponentScan, and @Configuration. The @EnableAutoConfiguration annotation activates Spring Boot’s auto-configuration mechanism. The @ComponentScan annotation instructs Spring to scan the specified package for components, configurations, and services, automatically registering them in the application context. The @Configuration annotation allows the class to define bean definitions and configuration settings. During startup, Spring Boot checks for dependencies on the classpath, identifies the type of application being created (web, data, security, etc.), and configures the appropriate beans Advanced Java Programming 390 automatically. It then starts the embedded server (if applicable) and makes the application ready to handle requests. Advantages of Using Spring Boot Spring Boot offers several advantages that make it ideal for modern software development. It simplifies configuration, reducing the need for manual setup. The use of embedded servers allows applications to be easily deployed and executed without external dependencies. Its auto-configuration and starter dependency system save time and reduce errors. The inclusion of DevTools improves productivity, and Actuator enhances application monitoring and management. Furthermore, Spring Boot is compatible with cloud platforms such as AWS, Azure, and Google Cloud, making it ideal for microservices and cloud-native application development. Example of a Simple Spring Boot Application Below is a basic example of a Spring Boot application that displays a greeting message. import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class HelloSpringBoot { public static void main(String[] args) { SpringApplication.run(HelloSpringBoot.class, args); } @GetMapping("/") public String sayHello() { return "Welcome to Spring Boot Application!"; Advanced Java Programming 391 } } In this example, the @SpringBootApplication annotation marks the main class as the entry point of the Spring Boot application. The @RestController annotation indicates that this class handles HTTP requests, and the @GetMapping annotation maps the root URL to the method sayHello(), which returns a simple greeting message. 5.4 Hibernate Framework Hibernate is an open-source Object Relational Mapping (ORM) framework for Java that simplifies the interaction between Java applications and relational databases. It provides a bridge between Java objects and database tables, allowing developers to work with objects instead of writing complex SQL queries. Hibernate maps Java classes to database tables and Java data types to corresponding SQL data types, making data handling more intuitive and object-oriented. By using Hibernate, developers can perform CRUD (Create, Read, Update, Delete) operations using its API or Hibernate Query Language (HQL) instead of traditional SQL. This approach significantly reduces boilerplate JDBC code, making development faster, cleaner, and easier to maintain. Why Hibernate is Used Traditional database interaction in Java is often handled through JDBC, which introduces several challenges. JDBC code is not easily portable across different database systems, making database switching difficult and time-consuming during a project’s lifecycle. Additionally, JDBC requires explicit exception handling for every database operation, leading to verbose and repetitive code that can quickly become difficult to manage. Another major limitation of JDBC is that it operates at the table level, offering no direct way to manage relationships between Java objects. Developers must manually write code to handle these associations, which adds to the complexity. Moreover, JDBC demands repetitive code for tasks like establishing connections, executing queries, and managing results, which reduces code readability and maintainability. Hibernate was developed to overcome these challenges. It eliminates repetitive JDBC code, simplifies database operations, and provides advanced features such as object-level relationships, caching, and transaction management. In addition, Hibernate supports database independence, allowing applications to switch between different databases with minimal changes. Advanced Java Programming 392 Key Features of Hibernate The most notable feature of Hibernate is its ability to perform Object-Relational Mapping (ORM). It maps Java classes to database tables and class attributes to table columns, enabling developers to work with data using Java objects rather than SQL queries. Hibernate also promotes database independence. Applications developed with Hibernate can run on multiple databases with little or no modification to the codebase, making them highly portable and flexible. Another key feature is Hibernate Query Language (HQL), a powerful, object-oriented query language that allows developers to write queries independent of the underlying database. Transaction management is seamlessly integrated in Hibernate, providing consistent and reliable handling of transactions through JDBC or Java Transaction API (JTA). Hibernate also enhances performance through caching mechanisms. It supports first-level caching at the session level and optional second-level caching across sessions to reduce database access time. Furthermore, Hibernate supports relationship mapping, allowing developers to define associations between objects such as one-to-one, one-to-many, many-to-one, and many-tomany relationships directly in Java classes. Hibernate Architecture Overview The architecture of Hibernate is modular and consists of several interrelated components that work together to manage database operations efficiently. Each layer in the architecture plays a specific role, from configuration and session management to transaction handling and query execution. Main Components of Hibernate Architecture 1. Configuration The Configuration class, found in the org.hibernate.cfg package, is the starting point of any Hibernate application. It is responsible for initializing and configuring the Hibernate framework. The configuration object reads the hibernate.cfg.xml file and mapping files that define the connection settings and mappings between Java classes and database tables. Example: Configuration cfg = new Configuration(); cfg.configure(); // Reads and validates hibernate.cfg.xml Advanced Java Programming 393 This process activates the Hibernate framework, reads the configuration files, validates them, and generates in-memory metadata representing the configuration. If the configuration is invalid, Hibernate throws an exception. Fig. 5.4 Hibernate Architecture. 2. SessionFactory The SessionFactory interface is responsible for creating and managing Session objects. It is a heavyweight and thread-safe object that should be created once per database and shared across the application. The SessionFactory also manages the second-level cache, improving performance across multiple sessions. Example: SessionFactory factory = cfg.buildSessionFactory(); 3. Session The Session interface represents a single unit of work between the application and the database. It provides methods for performing CRUD operations and manages the first-level Advanced Java Programming 394 cache, which holds objects within the session scope. A new session is typically opened from the SessionFactory for each transaction. Example: Session session = factory.openSession(); 4. Transaction The Transaction interface ensures atomic operations within the database, maintaining data integrity by providing mechanisms for commit and rollback. It ensures that either all database operations within a transaction are executed successfully or none are applied in case of an error. Example: Transaction tx = session.beginTransaction(); tx.commit(); // or tx.rollback(); 5. Query The Query interface allows developers to perform database operations using HQL, Criteria API, or native SQL queries. It is used to fetch, update, or delete records from the database in an object-oriented manner. Example: Query<Student> query = session.createQuery("from Student"); List<Student> students = query.list(); 6. Persistent Classes (Entities) Persistent classes represent the actual data model of the application. Each class corresponds to a database table, and its fields correspond to table columns. These classes are annotated with @Entity and must have a primary key annotated with @Id. Example: @Entity class Student { @Id Advanced Java Programming 395 private Long id; private String name; } Each object of a persistent class represents a single record (row) in the corresponding database table. 7. Mapping Mapping defines the relationship between Java classes and database tables. It specifies how class attributes correspond to table columns and how relationships between entities are structured. This can be achieved through annotations or XML mapping files. Example: @Entity @Table(name="student") class Student { @Id private int id; private String name; } Hibernate supports several types of relationships, such as one-to-one, one-to-many, many-toone, and many-to-many. 8. JDBC Layer Although Hibernate abstracts the underlying database interactions, it still uses JDBC internally to execute SQL queries and retrieve results. The JDBC layer manages connections, executes generated SQL statements, and returns data to Hibernate for conversion into Java objects. This layer handles connection pooling, result fetching, and communication with the database, ensuring smooth operation between the ORM and the relational data source. Advanced Java Programming 396 Hibernate Workflow The typical workflow of a Hibernate application follows a sequence of steps:  The application first loads the Hibernate configuration file (hibernate.cfg.xml) which contains the database connection settings and mapping details.  Hibernate uses this configuration to create a SessionFactory.  The application requests a Session from the SessionFactory.  Within the session, a transaction is initiated. CRUD operations are performed using Hibernate APIs or HQL queries.  The transaction is then either committed or rolled back depending on the outcome.  Hibernate translates the object-oriented operations into SQL commands and executes them through JDBC.  The results are retrieved and mapped back into Java objects that can be used by the application.  Through this process, Hibernate provides a seamless, object-oriented way to interact with relational databases while minimizing manual coding and configuration. Other Popular Frameworks Java has remained one of the most powerful and versatile programming languages for enterprise, desktop, and web applications. Over time, developers have built frameworks on top of Java to simplify complex tasks such as web development, configuration management, and server handling. These frameworks provide a foundation for developing scalable, maintainable, and efficient applications by minimizing repetitive coding and promoting reusability. Apart from the Spring Framework, there are several other popular frameworks that have significantly influenced Java development. Some of the most widely used among them include Apache Struts, JavaServer Faces (JSF), Play Framework, Grails Framework, and Vaadin Framework. Each of these frameworks offers unique features, architectural patterns, and benefits depending on the nature of the application being developed. Apache Struts Framework The Apache Struts Framework is one of the earliest and most widely used open-source Java web frameworks developed by the Apache Software Foundation. It is based on the ModelView-Controller (MVC) architecture, which separates the application logic from the user interface, promoting better maintainability and scalability. Struts gained popularity for its organized structure and ability to handle large, enterprise-level web applications efficiently.