Recent Posts

  • Jackson in Java – A Complete Guide with Examples

    Introduction

    Jackson is a widely used Java library for processing JSON data. It provides easy-to-use APIs for serializing Java objects to JSON and deserializing JSON into Java objects. Jackson is popular due to its high performance, flexible configuration, and powerful data-binding features.

    What is Jackson?

    Jackson is a high-performance JSON processor for Java. It helps convert Java objects to JSON and vice versa. The core class is ObjectMapper, and it also provides useful annotations for customization.

    Why Use Jackson?

    • Simple API
    • Fast and efficient
    • Handles complex data structures
    • Supports annotations for custom mapping
    • Flexible configuration

    1. Adding Jackson Dependency

    Maven

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
    </dependency>

    Gradle

    implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'

    After importing Jackson, we can use the ObjectMapper class to perform marshalling (serialization) and unmarshalling (deserialization) of JSON data.

    The ObjectMapper class is the core of Jackson’s functionality. It provides methods to:

    • writeValueAsString(Object obj) → Serialize Java object to JSON string.
    • writeValue(File resultFile, Object obj) → Serialize and write JSON directly to file.
    • readValue(String json, Class<T> valueType) → Deserialize JSON string to Java object.
    • readTree(String json) → Parse JSON into a tree of JsonNode.

    2. Serialization (Marshalling)

    What is Serialization?

    Serialization is the process of converting a Java object into a format (in this case, JSON) that can be easily stored or transmitted, and later reconstructed back into the original object. In the context of Jackson, serialization is also known as marshalling.

    When working with APIs, databases, or file storage, JSON is commonly used to represent structured data in a lightweight and human-readable format. Jackson’s ObjectMapper makes this conversion simple and efficient.

    2.1 Serialization Java Object to JSON

    ✔️ Example User.java Class
    public class User {
      private String name;
      private int age;
    
      public User() {}
    
      public User(String name, int age) {
        this.name = name;
        this.age = age;
      }
    
      // Getters and Setters
      public String getName() {
        return name;
      }
      public void setName(String name) {
        this.name = name;
      }
      public int getAge() {
        return age;
      }
      public void setAge(int age) {
        this.age = age;
      }
    }
    ✔️ Serialization Example
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class JacksonSerializationExample {
        public static void main(String[] args) throws Exception {
            ObjectMapper objectMapper = new ObjectMapper();//Create an instance of ObjectMapper
            User user = new User("Ashish Kumar", 30);
    
            String jsonString = objectMapper.writeValueAsString(user);//Serialize the Object to JSON String
    
            System.out.println("Serialized JSON: " + jsonString);
        }
    }

    Output:

    {"name":"Ashish Kumar","age":30}

    2.2 Writing JSON to a File

    ⚡ Instead of working only with strings, Jackson allows you to directly write JSON into files using:

    ✔️ Example

    import com.fasterxml.jackson.databind.ObjectMapper;
    import java.io.File;
    
    public class JacksonWriteToFileExample {
        public static void main(String[] args) {
            try {
                // Create ObjectMapper instance
                ObjectMapper objectMapper = new ObjectMapper();
    
                // Create a User object
                User user = new User("Ashish Kumar", 30);
    
                // Write the User object as JSON to the file "user.json"
                objectMapper.writeValue(new File("user.json"), user);
    
                System.out.println("JSON file has been written successfully.");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    ➔Output : Resulting File Content (user.json):
    {"name":"Ashish Kumar","age":30}

    👉This approach is helpful when persisting data or creating configuration files.

    Why Is This Useful?

    • Data Transmission: Serialized JSON is easy to send over HTTP in REST APIs.
    • Data Storage: Persist objects in a JSON file for later retrieval.
    • Logging and Debugging: Easily log object states in a readable format.
    • Configuration Files: Store application settings in JSON.

    3. Deserialization (Unmarshalling)

    What is Deserialization?

    Deserialization is the process of converting JSON data into a Java object. In the context of Jackson, this process is often called unmarshalling. It allows us to take a structured JSON string (or file) and transform it into a corresponding Java object so that we can easily work with the data in a type-safe manner.

    ✅ Why Is Deserialization Important?

    • Reading API Responses: When calling REST APIs, the response is often in JSON format. To work with the data in Java, we deserialize it into objects.
    • Reading Configuration Files: JSON configuration files are commonly used for application settings.
    • Data Persistence: Deserialize stored JSON files back into Java objects for processing or display.

    ✅ How Does Jackson Perform Deserialization?

    Once Jackson is imported and the ObjectMapper is available, we use it to convert JSON strings or files into Java objects.

    3.1 Deserialization JSON String to Java Object

    ✔️ Example JSON String
    {"name":"Simone","age":28}
    Deserialization Example
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class JacksonDeserializationExample {
        public static void main(String[] args) {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                String jsonString = "{\"name\":\"Bhasker\",\"age\":28}";
    
                User user = objectMapper.readValue(jsonString, User.class);
    
                System.out.println("Name: " + user.getName());
                System.out.println("Age: " + user.getAge());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    Output:

    Name: Bhasker
    Age: 28

    👉This shows how the JSON string is parsed and converted into a User object.

    3.2 Deserializing from a JSON File to Java Object

    ✔️ Example JSON File (user.json):
    {
        "name": "Bhasker",
        "age": 28
    }
    ✔️ Example Java Code:
    import com.fasterxml.jackson.databind.ObjectMapper;
    import java.io.File;
    
    public class JacksonReadFileExample {
        public static void main(String[] args) {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                File jsonFile = new File("user.json");
    
                User user = objectMapper.readValue(jsonFile, User.class);
    
                System.out.println("Name: " + user.getName());
                System.out.println("Age: " + user.getAge());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    👉This approach is helpful when working with large or persistent JSON data stored in files.

    Output :

    Name: Bhasker
    Age: 28

    ✅ How Does Jackson Know How to Map JSON to Java Object?

    Jackson relies on:

    • Default Constructor: The target class must have a no-argument constructor (can be implicit).
    • Getters and Setters: Jackson uses public setters to set values during deserialization.
    • Field Matching: JSON property names must match Java field names, unless annotations (like @JsonProperty) are used to map them explicitly.

    4. Jackson Annotations

    Jackson provides useful annotations to control serialization and deserialization.

    Common Jackson Annotations

    • @JsonProperty → Maps a Java field to a specific JSON property name
    • @JsonIgnore → Prevents a specific field from being serialized or deserialized.
    • @JsonInclude → Controls inclusion of properties during serialization (e.g., omit nulls).
    • @JsonIgnoreProperties → Prevents multiple unknown fields from causing exceptions during deserialization.
    • @JsonCreator → Useful when deserializing immutable objects with final fields and no default constructor.

    Learn more about Jackson Annotations here 👉 Common Jackson Annotations – A Complete Guide

    ✔️ Example : Employee.java

    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    public class Employee {
        @JsonProperty("full_name")
        private String name;
        private int age;
    
        @JsonIgnore
        private String password;
    
        public Employee() { }
    
        public Employee(String name, int age, String password) {
            this.name = name;
            this.age = age;
            this.password = password;
        }
    
        // Getters and Setters
    }
    
    Example Serialization
    ObjectMapper objectMapper = new ObjectMapper();
    Employee emp = new Employee("Ashish", 30, "secure123");
    
    String jsonString = objectMapper.writeValueAsString(emp);
    
    System.out.println(jsonString);
    

    Output:

    {"full_name":"Ashish","age":30}
    

    5. Jackson Tree Model (JsonNode)

    The Tree Model in Jackson represents JSON data as a hierarchical tree of JsonNode objects.

    Learn more about Jackson Tree Model (JsonNode) 👉 Jackson Tree Model (JsonNode) – Complete JSON Guide .

    Example : How to Parse JSON into Tree Model

    Json File

    {
        "name": "Ashish Kumar",
        "age": 30,  
    }
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class JacksonTreeExample {
        public static void main(String[] args) throws Exception {
            String json = "{\"name\":\"Ashish\",\"age\":30}";
            ObjectMapper mapper = new ObjectMapper();
            JsonNode node = mapper.readTree(json);
    
            System.out.println("Name: " + node.get("name").asText());
            System.out.println("Age: " + node.get("age").asInt());
        }
    }

    Output :

    Name: Ashish   
    Age: 30 

    6. Handling Collections

    ✅ What Is Collection Handling?

    Collection handling refers to the process of deserializing a JSON array into a Java collection (usually a List, Set, or Map) of objects, and serializing a Java collection back into a JSON array.

    For detailed information, please refer to the👉 Handling Collections in Jackson – Detailed Explanation

    Example :

    Input JSON String:

    [
        {"name":"Bhasker","age":28},
        {"name":"Ashish","age":30}
    ]
    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import java.util.List;
    
    public class JacksonListExample {
      public static void main(String[] args) throws Exception {
        String json = "[{\"name\":\"Bhasker\",\"age\":28}, {\"name\":\"Ashish\",\"age\":30}]";
    
        ObjectMapper mapper = new ObjectMapper();
        List < User > users = mapper.readValue(json, new TypeReference < List < User >> () {});
    
        for (User u: users) {
          System.out.println(u.getName() + " - " + u.getAge());
        }
      }
    }

    Output :

    Bhasker - 28
    Ashish - 30

    7. Reading List of Objects from JSON File

    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import java.io.File;
    import java.util.List;
    
    public class JacksonReadListFromFileExample {
        public static void main(String[] args) throws Exception {
            ObjectMapper objectMapper = new ObjectMapper();
            File jsonFile = new File("users.json");
    
            List<User> users = objectMapper.readValue(jsonFile, new TypeReference<List<User>>() {});
    
            for (User user : users) {
                System.out.println(user.getName() + " - " + user.getAge());
            }
        }
    }
    

    8. Pretty Printing JSON

    When working with JSON data, especially for debugging, configuration files, or manual inspection, a compact JSON string without any formatting can be hard to read.
    By default, Jackson produces a compact JSON output like this:

    {"name":"Ashish Kumar","age":30}

    This is fine for machine processing but not ideal for humans.
    To make the JSON output more readable, we use Pretty Printing to add line breaks, indentation, and proper spacing.

    ✔️ Example

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    
    public class JacksonPrettyPrintExample {
        public static void main(String[] args) throws Exception {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
    
            User user = new User("Ashish Kumar", 30);
    
            String prettyJson = objectMapper.writeValueAsString(user);
    
            System.out.println(prettyJson);
        }
    }
    

    Output:

    {
      "name" : "Ashish Kumar",
      "age" : 30
    }
    

    9. Common Exceptions to Handle

    • JsonProcessingException
    • UnrecognizedPropertyException
    • MismatchedInputException

    Summary of ObjectMapper Usage

    OperationMethod Example
    Serialize Object to StringwriteValueAsString(obj)
    Serialize Object to FilewriteValue(new File("output.json"), obj)
    Deserialize String to ObjectreadValue(jsonString, User.class)
    Deserialize File to ObjectreadValue(new File("user.json"), User.class)
    Read Tree ModelreadTree(jsonString)
    Deserialize Array from FilereadValue(new File("users.json"), new TypeReference<List<User>>(){})
    Pretty PrintEnable SerializationFeature.INDENT_OUTPUT

    🎯Conclusion

    Jackson is a powerful and flexible library that makes working with JSON in Java effortless. By mastering serialization, deserialization, annotations, tree model, file handling, collections, and pretty printing, you can efficiently handle JSON data in any Java project.

  • String Template in Java 21 – A Complete Guide

    ✅ What is a String Template in Java 21?

    A String Template is a structured way to define a string with embedded expressions, which are evaluated and inserted in a safe and readable way. It improves code clarity and avoids issues like manual escaping or mistakes in formatting.

    ✔️ Syntax Example of String Template (Java 21)

    String name = "Ashish";
    int age = 30;
    
    String result = STR."My name is \{name} and I am \{age} years old.";
    System.out.println(result);
    
    • STR."..." is a string template literal.
    • Inside the template, expressions are written as \{expression}.
    • At compile-time, these expressions are evaluated and injected into the string.

    ✅ Advantages of String Templates

    FeatureBenefit
    Easier SyntaxCleaner and readable syntax compared to concatenation or String.format().
    Compile-Time SafetyErrors in embedded expressions are caught at compile time.
    Automatic EscapingNo need to manually handle escaping of quotes or special characters.
    Structured FormattingIdeal for complex multi-line templates.

    ✔️ Example Compared to Earlier Approaches

    ➤ Pre-Java 21 (Traditional way):

    String name = "Ashish";
    int age = 30;
    
    // Using concatenation
    String result1 = "My name is " + name + " and I am " + age + " years old.";
    
    // Using String.format
    String result2 = String.format("My name is %s and I am %d years old.", name, age);
    
    System.out.println(result1);
    System.out.println(result2);
    

    ➤ Java 21 String Template Way:

    String name = "Ashish";
    int age = 30;
    
    String result = STR."My name is \{name} and I am \{age} years old.";
    System.out.println(result);
    

    ✅ Key Differences Between String Template and Earlier Approaches

    AspectPre-Java 21Java 21 String Template
    SyntaxVerbose (concatenation, String.format)Cleaner and easier to read
    SafetyRuntime errors if format string is wrongCompile-time checks
    EscapingManual, error-proneHandled automatically
    PerformanceModerate, because of repeated concatenationsEfficient at compile time
    Multi-line stringsComplicated, need workaroundsSupported naturally with templates
    ReusabilityHarderTemplates can be reusable components

    ✅ When to Prefer String Templates?

    • For dynamic string generation in a readable and safe way.
    • When working with multi-line strings (e.g., generating HTML or JSON templates).
    • When avoiding manual concatenation and improving code maintainability.

    ⚠️ Important Note

    • String Templates in Java 21 are still in Preview Mode.
    • You need to enable preview features to use them:
      javac --enable-preview and java --enable-preview.

    ✅ Conclusion

    String Templates in Java 21 represent a modern, safe, and clean way of working with dynamic strings compared to the older cumbersome ways. It simplifies code, reduces bugs, and improves readability.

    Let me know if you want me to provide a detailed blog-style explanation with examples and use-cases.

  • Class and Object in Java – A Complete Beginner’s Guide

    In Java, Object-Oriented Programming (OOP) plays a crucial role in structuring applications using real-world concepts. The two most fundamental concepts of OOP are Class and Object. Understanding these is essential for every Java developer.

    ✅ What is a Class in Java?

    A Class is a blueprint or template for creating objects. It defines the properties (attributes or fields) and behaviors (methods) that the objects created from the class will have.

    🔍 Key Points About Class:

    • Acts like a template.
    • Defines fields (variables) and methods (functions).
    • Does not consume memory directly.

    ✅ Class Syntax Example:

    public class Car {
        // Fields (Properties)
        String color;
        String model;
        int year;
    
        // Method (Behavior)
        public void displayDetails() {
            System.out.println("Model: " + model);
            System.out.println("Color: " + color);
            System.out.println("Year: " + year);
        }
    }
    

    ✅ What is an Object in Java?

    An Object is an instance of a class. It occupies memory and holds actual values for the fields defined in the class. Through the object, you can access the methods and variables defined in the class.

    ✅ Object Creation Example:

    public class Main {
        public static void main(String[] args) {
            // Creating an object of the Car class
            Car myCar = new Car();
    
            // Assign values to the fields
            myCar.model = "Toyota Camry";
            myCar.color = "Red";
            myCar.year = 2022;
    
            // Call method using the object
            myCar.displayDetails();
        }
    }
    

    ✅ Output:

    Model: Toyota Camry
    Color: Red
    Year: 2022
    

    ✅ Class vs Object – Quick Comparison

    ClassObject
    Blueprint of real-world entityActual entity created from the class
    Contains fields and methodsHolds data and behavior of the instance
    No memory allocationMemory is allocated when object is created
    Syntax: class ClassName { ... }Syntax: ClassName obj = new ClassName();

    ✅ Why Use Class and Object?

    • Encapsulation: Organizes data and behavior in one unit.
    • Reusability: Once the class is created, multiple objects can be created and reused.
    • Abstraction: Internal implementation is hidden; you interact only via methods.
    • Real-World Representation: Classes represent real-world entities, making it easy to model complex problems.

    ✅ Example With Multiple Objects

    public class Main {
        public static void main(String[] args) {
            Car car1 = new Car();
            car1.model = "Honda Civic";
            car1.color = "Blue";
            car1.year = 2020;
    
            Car car2 = new Car();
            car2.model = "Ford Mustang";
            car2.color = "Black";
            car2.year = 2021;
    
            System.out.println("Car 1 Details:");
            car1.displayDetails();
    
            System.out.println("\nCar 2 Details:");
            car2.displayDetails();
        }
    }
    

    ✅ Key Concepts to Remember

    • A class defines what an object will look like and what it can do.
    • An object is a real instance of the class that holds actual values and can perform actions (methods).
    • You can create multiple objects from the same class, each with its own state.

    ✅ Conclusion

    Understanding Class and Object is the first step toward mastering Java’s object-oriented programming. It allows you to model real-world entities and perform structured programming with easy code reusability and better maintainability.

  • String vs StringBuffer vs StringBuilder in Java Guide

    When working with text data in Java, there are three commonly used classes to handle strings:

    1. String
    2. StringBuffer
    3. StringBuilder

    Each of these serves a different purpose and has its own advantages and limitations. Understanding when and how to use them is key for writing efficient Java programs.

    ✅ 1. String

    • Immutable Object: Once a String object is created, its value cannot be changed.
    • Every modification creates a new String object in memory.
    • Useful when the string content doesn’t change often.

    🔧 Example:

    public class StringExample {
        public static void main(String[] args) {
            String text = "Java Knowledge Base";
            System.out.println("Original String: " + text);
    
            // Concatenation creates a new String object
            text = text + " - Learn Java Effectively";
            System.out.println("Modified String: " + text);
        }
    }
    

    ✅ Output:

    Original String: Java Knowledge Base
    Modified String: Java Knowledge Base - Learn Java Effectively
    

    Key Point: Inefficient for many modifications due to creation of new objects and higher memory consumption.

    ✅ 2. StringBuffer

    • Mutable Class: Allows modification of the string content without creating new objects.
    • Thread-Safe: All methods are synchronized.
    • Suitable when thread safety is required.

    🔧 Example:

    public class StringBufferExample {
        public static void main(String[] args) {
            StringBuffer textBuffer = new StringBuffer("Java Knowledge Base");
            System.out.println("Original StringBuffer: " + textBuffer);
    
            // Append text
            textBuffer.append(" - Learn Java Effectively");
            System.out.println("Modified StringBuffer: " + textBuffer);
    
            // Insert text
            textBuffer.insert(5, " Awesome");
            System.out.println("After Insert: " + textBuffer);
        }
    }
    

    ✅ Output:

    Original StringBuffer: Java Knowledge Base
    Modified StringBuffer: Java Knowledge Base - Learn Java Effectively
    After Insert: Java Awesome Knowledge Base - Learn Java Effectively
    

    Key Point: Suitable for multi-threaded environments but slightly slower than StringBuilder.

    ✅ 3. StringBuilder

    • Mutable Class: Like StringBuffer, but not synchronized (not thread-safe).
    • Faster than StringBuffer due to the lack of synchronization.
    • Recommended when working in a single-threaded context.

    🔧 Example:

    public class StringBuilderExample {
        public static void main(String[] args) {
            StringBuilder textBuilder = new StringBuilder("Java Knowledge Base");
            System.out.println("Original StringBuilder: " + textBuilder);
    
            // Append text
            textBuilder.append(" - Learn Java Effectively");
            System.out.println("Modified StringBuilder: " + textBuilder);
    
            // Insert text
            textBuilder.insert(5, " Awesome");
            System.out.println("After Insert: " + textBuilder);
        }
    }
    

    ✅ Output:

    Original StringBuilder: Java Knowledge Base
    Modified StringBuilder: Java Knowledge Base - Learn Java Effectively
    After Insert: Java Awesome Knowledge Base - Learn Java Effectively
    

    Key Point: Best choice when working in a single-threaded environment and performance is critical.

    ⚔️ Comparison Table

    FeatureStringStringBufferStringBuilder
    MutabilityImmutableMutableMutable
    Thread SafetyNot applicableThread-safe (synchronized)Not thread-safe
    PerformanceSlow for modificationsSlower than StringBuilderFast (better performance)
    Use CaseStatic text or rarely changedMulti-threaded contextSingle-threaded context
    Memory UsageHigh (creates new objects)ModerateModerate

    ✅ Conclusion

    • Use String when the string content does not change.
    • Use StringBuffer when you need thread safety.
    • Use StringBuilder for efficient single-threaded string manipulation
  • Immutable Class in Java – Complete Guide with Examples

    An Immutable Class in Java is a class whose instances (objects) cannot be modified after creation. Once an object of an immutable class is created, its state remains constant throughout the lifetime of the object. Immutable objects are widely used in multi-threaded environments because they are inherently thread-safe and don’t require synchronization.

    ✅ Why Use Immutable Class?

    • Thread-safety: Immutable objects can be safely shared between multiple threads without additional synchronization.
    • Caching and Performance: Immutable objects can be cached and reused, reducing the need to create new objects frequently.
    • Security: Since the object’s state can’t be changed, it prevents accidental or malicious modifications.

    ✅ Key Characteristics of Immutable Class

    1. Final Class: The class should be declared final to prevent subclassing which might override methods and break immutability.
    2. Private Final Fields: All fields should be declared private and final to prevent direct access and modification.
    3. No Setter Methods: Do not provide setters. Only provide getters to expose field values.
    4. Deep Copy in Constructor: When the class has mutable fields (like arrays or collections), perform deep copying in the constructor and in the getter methods to prevent external modification.

    ✅ Example of Immutable Class

    import java.util.Date;
    
    public final class Employee {
    
        private final int id;
        private final String name;
        private final Date joiningDate;
    
        // Constructor performs deep copy of mutable objects
        public Employee(int id, String name, Date joiningDate) {
            this.id = id;
            this.name = name;
            this.joiningDate = new Date(joiningDate.getTime()); // Defensive copy
        }
    
        public int getId() {
            return id;
        }
    
        public String getName() {
            return name;
        }
    
        public Date getJoiningDate() {
            return new Date(joiningDate.getTime()); // Return copy to maintain immutability
        }
    
        @Override
        public String toString() {
            return "Employee { " +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", joiningDate=" + joiningDate +
                    " }";
        }
    }
    

    ✅ Usage Example

    import java.util.Date;
    
    public class Main {
        public static void main(String[] args) {
            Date date = new Date();
            Employee emp = new Employee(101, "John Doe", date);
    
            System.out.println(emp);
    
            // Attempt to modify original date object
            date.setTime(0);
    
            // The internal state of Employee remains unchanged
            System.out.println(emp);
    
            // Attempt to modify date retrieved via getter
            emp.getJoiningDate().setTime(0);
    
            // Employee’s joiningDate remains immutable
            System.out.println(emp);
        }
    }
    

    ✅ Output Explanation

    • Even though we modify the original Date object and the one obtained from getJoiningDate(), the internal state of the Employee object remains unchanged.
    • This demonstrates how defensive copying preserves immutability.

    ✅ Best Practices for Immutable Class

    • Use primitive types and immutable objects (like String, Integer) for fields whenever possible.
    • For fields that are mutable objects (like Date, arrays, or collections), always use deep copy in constructors and getters.
    • Mark the class as final to prevent subclassing.
    • Avoid exposing mutable fields directly.

    ✅ When Should You Use Immutable Classes?

    • When thread-safety is crucial.
    • When object state should not change after creation.
    • For value objects such as coordinates, currency, or configurations.
    • To avoid defensive copying outside your control.

    ✅ Advantages and Disadvantages

    AdvantagesDisadvantages
    Thread-safe by designHigher memory footprint if many copies needed
    Simple to reason about object statePerformance overhead due to object copying
    Can be safely shared across threadsCannot be partially updated, needs recreation

    ✅ Conclusion

    Immutable classes are a cornerstone of robust and thread-safe application design in Java. They offer simplicity, safety, and reliability in concurrent environments. However, they should be used judiciously where immutability is beneficial, and care must be taken with mutable fields.

  • Abstract Class in Java

    What is an Abstract Class in Java?

    An Abstract Class is a class in Java that cannot be instantiated directly. It is used as a base class and is meant to be extended by other classes. Abstract classes can contain abstract methods (without implementation) and concrete methods (with implementation).

    An abstract class helps in providing a common template to its child classes and enforcing some method implementation through abstraction, but still allowing common method implementations.

    Why Use Abstract Classes?

    • Abstract classes allow partial implementation of functionality that is common to multiple subclasses, reducing code duplication.
    • They also help enforce method overriding by requiring subclasses to implement abstract methods.
    • By using abstract classes, you follow key OOP principles, such as Abstraction and promoting Code Reusability.

    Abstract Class Syntax Example

    // Abstract class
    abstract class Animal {
        
        // Abstract method (must be implemented by subclass)
        abstract void sound();
        
        // Concrete method (common functionality)
        void eat() {
            System.out.println("This animal eats food");
        }
    }
    
    // Concrete subclass
    class Dog extends Animal {
        
        // Implementation of abstract method
        void sound() {
            System.out.println("Dog barks");
        }
    }
    
    // Main class to run the program
    public class AbstractClassDemo {
        public static void main(String[] args) {
            Animal myDog = new Dog();  // Animal reference, Dog object
            myDog.eat();               // Calls concrete method from abstract class
            myDog.sound();             // Calls overridden method
        }
    }
    

    Output:

    This animal eats food
    Dog barks
    

    Key Points About Abstract Class

    FeatureDescription
    InstantiationCannot create object of abstract class directly
    Abstract MethodsDeclared with abstract keyword; No method body
    Concrete MethodsCan have implemented methods
    ConstructorAbstract classes can have constructors
    VariablesCan have instance variables
    Access ModifiersAbstract methods can have public or protected access modifiers
    Subclass ResponsibilityConcrete subclass must implement all abstract methods or itself be abstract

    Abstract Class vs Interface

    Abstract ClassInterface
    Can have constructorsNo constructors (before Java 8)
    Can have abstract + concrete methodsJava 8+: Can have default methods with implementation
    Can maintain state (instance variables)No instance variables (except static and final)
    Supports method access modifiers (private/protected/public)All methods are public (by default)
    Used when classes are closely relatedUsed for defining capabilities or behavior across unrelated classes

    When to Use Abstract Class?

    • Use an abstract class when a base class with common functionality should be shared by multiple subclasses.
    • It is helpful in cases where partial implementation is needed.
    • Abstract classes also improve code reusability while enforcing implementation rules in subclasses.

      Example of Abstract Class with Multiple Subclasses

      abstract class Shape {
          abstract void area();
          
          void display() {
              System.out.println("This is a shape");
          }
      }
      
      class Circle extends Shape {
          int radius = 5;
          
          void area() {
              System.out.println("Area of Circle: " + (3.14 * radius * radius));
          }
      }
      
      class Rectangle extends Shape {
          int length = 10, width = 5;
          
          void area() {
              System.out.println("Area of Rectangle: " + (length * width));
          }
      }
      
      public class ShapeDemo {
          public static void main(String[] args) {
              Shape c = new Circle();
              c.display();
              c.area();
              
              Shape r = new Rectangle();
              r.display();
              r.area();
          }
      }
      

      Output:

      This is a shape
      Area of Circle: 78.5
      This is a shape
      Area of Rectangle: 50
      

      Best Practices

      • Prefer abstract classes when classes share a strong relationship and common implementation.
      • Avoid creating large abstract classes with too many abstract methods.
      • Keep it clean: Abstract class should focus on providing a template and common functionality.

      Summary

      An abstract class in Java provides a blueprint for subclasses. It helps enforce rules by using abstract methods, while at the same time allowing code reuse through concrete methods. Moreover, it serves as a powerful tool when designing applications that follow key OOP principles, such as abstraction, inheritance, and polymorphism. In addition, abstract classes promote a clean and scalable architecture by clearly defining a template for subclasses.

    • Java Interface Tutorial – A Complete Guide

      In Java, an interface is a blueprint for a class. It is a reference type, similar to a class, and it can contain abstract methods, default methods, static methods, and constants. Interfaces are used to achieve abstraction and multiple inheritance in Java.

      1. What is an Interface in Java?

      An interface is a collection of abstract methods and constants. Unlike classes, interfaces cannot be instantiated. Classes implement interfaces to provide concrete behavior.

      Key points:

      • Defines what a class should do, not how it does it.
      • Promotes loose coupling between classes.
      • Supports multiple inheritance (a class can implement multiple interfaces).

      2. Syntax of an Interface

      interface InterfaceName {
          // constant declaration
          int MAX_VALUE = 100;  // public, static, final by default
      
          // abstract method
          void method1();       // public and abstract by default
      
          // default method (since Java 8)
          default void defaultMethod() {
              System.out.println("This is a default method.");
          }
      
          // static method (since Java 8)
          static void staticMethod() {
              System.out.println("This is a static method.");
          }
      }
      

      3. Implementing an Interface

      A class implements an interface using the implements keyword. It must override all abstract methods of the interface.

      Example:

      interface Vehicle {
          void start();
          void stop();
      }
      
      class Car implements Vehicle {
          @Override
          public void start() {
              System.out.println("Car is starting...");
          }
      
          @Override
          public void stop() {
              System.out.println("Car is stopping...");
          }
      }
      
      public class InterfaceDemo {
          public static void main(String[] args) {
              Vehicle myCar = new Car();
              myCar.start();
              myCar.stop();
          }
      }
      

      Output:

      Car is starting...
      Car is stopping...
      

      4. Multiple Interface Implementation

      Java allows a class to implement multiple interfaces, which is a way to achieve multiple inheritance.

      interface Engine {
          void startEngine();
      }
      
      interface GPS {
          void navigate();
      }
      
      class SmartCar implements Engine, GPS {
          @Override
          public void startEngine() {
              System.out.println("Engine started!");
          }
      
          @Override
          public void navigate() {
              System.out.println("Navigating to destination...");
          }
      }
      
      public class MultipleInterfaceDemo {
          public static void main(String[] args) {
              SmartCar car = new SmartCar();
              car.startEngine();
              car.navigate();
          }
      }
      

      Output:

      Engine started!
      Navigating to destination...
      

      5. Default and Static Methods in Interface

      • Default Methods: Allow interfaces to have method implementations.
      • Static Methods: Belong to the interface, not to the object.
      interface Printer {
          void print();
      
          default void printPreview() {
              System.out.println("Printing preview...");
          }
      
          static void staticMethod() {
              System.out.println("Static method in interface.");
          }
      }
      
      class DocumentPrinter implements Printer {
          @Override
          public void print() {
              System.out.println("Printing document...");
          }
      }
      
      public class DefaultStaticDemo {
          public static void main(String[] args) {
              DocumentPrinter dp = new DocumentPrinter();
              dp.print();
              dp.printPreview();       // default method
              Printer.staticMethod();  // static method
          }
      }
      

      Output:

      Printing document...
      Printing preview...
      Static method in interface.
      

      6. Key Features of Interfaces

      FeatureDescription
      Abstract MethodsMethods without body (implemented by class).
      Default MethodsMethods with default implementation.
      Static MethodsBelongs to interface, not objects.
      ConstantsVariables are public, static, final by default.
      Multiple InheritanceA class can implement multiple interfaces.

      7. Practical Example: Real-World Scenario

      Imagine an online payment system. Different payment methods (CreditCard, PayPal) can implement the Payment interface.

      interface Payment {
          void pay(double amount);
      }
      
      class CreditCardPayment implements Payment {
          @Override
          public void pay(double amount) {
              System.out.println("Paid " + amount + " using Credit Card.");
          }
      }
      
      class PayPalPayment implements Payment {
          @Override
          public void pay(double amount) {
              System.out.println("Paid " + amount + " using PayPal.");
          }
      }
      
      public class PaymentDemo {
          public static void main(String[] args) {
              Payment payment1 = new CreditCardPayment();
              Payment payment2 = new PayPalPayment();
      
              payment1.pay(500);
              payment2.pay(1000);
          }
      }
      

      Output:

      Paid 500.0 using Credit Card.
      Paid 1000.0 using PayPal.
      

      8. Interface vs Abstract Class

      AspectInterfaceAbstract Class
      MethodsAbstract by default, can have default & static methodsCan have abstract & concrete methods
      Variablespublic, static, finalAny access modifier, non-final allowed
      InheritanceMultiple interfaces allowedOnly single inheritance
      Use caseDefine contract for unrelated classesShare code between closely related classes

      9. Best Practices

      1. Use interfaces to define contracts for classes.
      2. Prefer interfaces over abstract classes when you need multiple inheritance.
      3. Keep interfaces focused: one interface = one responsibility.
      4. Use default methods sparingly; they are mostly for backward compatibility.

      Conclusion:


      Interfaces are fundamental in Java for abstraction, multiple inheritance, and loose coupling. Understanding them helps in designing flexible and maintainable systems.

    • Ways to Create an Object in Java

      Creating objects in Java is a fundamental concept in Object-Oriented Programming (OOP). In Java, there are multiple ways to create an object, each having its specific use case, advantages, and implications. This tutorial explains all possible ways in detail with scenarios and examples.

      ✅ 1. Using new Keyword (Most Common Approach)

      How It Works:

      • The new keyword allocates memory for the new object and calls the constructor.

      Example:

      public class Student {
          String name;
          int age;
      
          public Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Student s1 = new Student("Alice", 20);
              System.out.println(s1.name + " - " + s1.age);
          }
      }
      

      Scenario:

      Use this approach for general purpose object creation when you want a fresh object.

      ✅ 2. Using Class.forName() and newInstance() (Reflection)

      How It Works:

      • The class name is passed as a string, and the object is created at runtime.
      • Suitable for dynamic object creation.

      Example:

      public class Student {
          public void display() {
              System.out.println("Student Object Created using Reflection");
          }
      }
      
      public class Main {
          public static void main(String[] args) throws Exception {
              Class<?> cls = Class.forName("Student");
              Student s = (Student) cls.getDeclaredConstructor().newInstance();
              s.display();
          }
      }
      

      Scenario:

      Useful when the class name is known only at runtime (e.g., in plugin architectures).

      ✅ 3. Using clone() Method (Object Cloning)

      How It Works:

      • Creates a copy of an existing object.
      • Class must implement Cloneable interface and override clone().

      Example:

      public class Student implements Cloneable {
          String name;
          int age;
      
          public Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          @Override
          protected Object clone() throws CloneNotSupportedException {
              return super.clone();
          }
      }
      
      public class Main {
          public static void main(String[] args) throws CloneNotSupportedException {
              Student s1 = new Student("Bob", 22);
              Student s2 = (Student) s1.clone();
              System.out.println(s2.name + " - " + s2.age);
          }
      }
      

      Scenario:

      Useful when you want to create a duplicate object without invoking the constructor.

      ✅ 4. Using Deserialization

      How It Works:

      • Converts a byte stream back into an object.
      • Useful for persisting and transferring objects.

      Example:

      import java.io.*;
      
      public class Student implements Serializable {
          String name;
          int age;
      
          public Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      
      public class Main {
          public static void main(String[] args) throws Exception {
              // Serialize
              Student s1 = new Student("Charlie", 24);
              ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.ser"));
              out.writeObject(s1);
              out.close();
      
              // Deserialize
              ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.ser"));
              Student s2 = (Student) in.readObject();
              in.close();
      
              System.out.println(s2.name + " - " + s2.age);
          }
      }
      

      Scenario:

      Ideal for restoring objects from storage or transferring them across networks.

      ✅ 5. Using Factory Method (Design Pattern)

      How It Works:

      • Factory methods provide a controlled way to create objects.

      Example:

      class Student {
          private String name;
          private int age;
      
          private Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public static Student createStudent(String name, int age) {
              return new Student(name, age);
          }
      
          public void display() {
              System.out.println(name + " - " + age);
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Student s = Student.createStudent("David", 25);
              s.display();
          }
      }
      

      Scenario:

      Recommended when object creation logic is complex or needs encapsulation.

      ✅ 6. Using Builder Pattern

      How It Works:

      • Provides flexibility in object creation with multiple optional parameters.

      Example:

      public class Student {
          private String name;
          private int age;
          private String course;
      
          private Student(StudentBuilder builder) {
              this.name = builder.name;
              this.age = builder.age;
              this.course = builder.course;
          }
      
          public static class StudentBuilder {
              private String name;
              private int age;
              private String course;
      
              public StudentBuilder setName(String name) {
                  this.name = name;
                  return this;
              }
      
              public StudentBuilder setAge(int age) {
                  this.age = age;
                  return this;
              }
      
              public StudentBuilder setCourse(String course) {
                  this.course = course;
                  return this;
              }
      
              public Student build() {
                  return new Student(this);
              }
          }
      
          public void display() {
              System.out.println(name + ", " + age + " years, Course: " + course);
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Student s = new Student.StudentBuilder()
                  .setName("Eve")
                  .setAge(23)
                  .setCourse("Computer Science")
                  .build();
              s.display();
          }
      }
      

      Scenario:

      Best for creating objects with many optional fields.

      ✅ Summary of All Ways

      MethodScenario Use Case
      new KeywordSimple, standard object creation
      ReflectionDynamic class loading and instantiation
      clone()Object duplication without calling constructor
      DeserializationObject persistence and transfer
      Factory MethodEncapsulated creation logic
      Builder PatternComplex object creation with optional fields

      🎯 Conclusion

      Understanding various ways of object creation allows developers to choose the most suitable method depending on the scenario. Whether for simplicity, performance, or flexibility, each method has its advantages.

    • Abstraction in Java :-A Complete Guide

      1. What is Abstraction?

      Abstraction is one of the core OOP concepts. It is the process of hiding the internal details of how something works and only exposing the essential features or behaviors to the user.

      • You don’t show the “how”, only the “what it does”.
      • It allows the user to use functionality without worrying about internal implementation.

      In Java, abstraction is achieved by:

      1. Abstract Classes
      2. Interfaces

      2. How Abstraction Works

      Key Points:

      1. Hide Implementation: Users of a class don’t need to know the inner details of methods. They just call the method.
      2. Only Show Behavior: You provide a method signature (name, input/output), but the internal logic can be hidden.
      3. Restrict Direct Access: Internal variables can be private so they cannot be accessed directly, only through methods.

      Example Using Abstract Class

      // Abstract class
      abstract class Vehicle {
          // Abstract method (no implementation)
          abstract void startEngine();
      
          // Regular method (implementation can be provided)
          void fuelType() {
              System.out.println("Fuel type is Petrol/Diesel/Electric");
          }
      }
      
      // Child class provides implementation
      class Car extends Vehicle {
          @Override
          void startEngine() {
              System.out.println("Car engine starts with a key or button");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Vehicle myCar = new Car();  // Reference type is abstract class
              myCar.startEngine();        // Calls implemented method in Car
              myCar.fuelType();           // Calls inherited method
          }
      }
      

      Explanation:

      • Vehicle class hides how the engine starts.
      • Car class provides the implementation of startEngine().
      • The user only interacts with the startEngine() method without knowing internal details.
      • This is abstraction in action.

      Example Using Interface

      interface RemoteControl {
          void turnOn();
          void turnOff();
      }
      
      class TV implements RemoteControl {
          @Override
          public void turnOn() {
              System.out.println("TV is turned ON");
          }
      
          @Override
          public void turnOff() {
              System.out.println("TV is turned OFF");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              RemoteControl myTV = new TV();
              myTV.turnOn();   // Only uses the method
              myTV.turnOff();  // No idea how the internal logic works
          }
      }
      

      Explanation:

      • RemoteControl interface only defines what actions are possible.
      • TV class defines how those actions are executed.
      • The user is only aware of what methods they can call.

      3. Restricting Data and Only Showing Functions

      • Use private variables to hide data.
      • Provide public methods (getter/setter) to access the data.
      • This prevents direct modification and ensures controlled access.
      class BankAccount {
          private double balance;  // Hidden from the user
      
          // Method to deposit money (control access)
          public void deposit(double amount) {
              if (amount > 0) {
                  balance += amount;
                  System.out.println("Deposited: " + amount);
              }
          }
      
          // Method to check balance
          public double getBalance() {
              return balance;
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              BankAccount account = new BankAccount();
              account.deposit(500);
              System.out.println("Balance: " + account.getBalance());
          }
      }
      
      • User cannot directly access balance.
      • User can only interact through methods, which is controlled abstraction.

      4. Summary of Abstraction

      FeatureDescription
      PurposeHide implementation, show only functionality
      How to implementAbstract classes or interfaces
      BenefitsSimplifies complexity, improves security, allows flexibility
      Restrict dataUse private variables + public methods
      Real-life analogyDriving a car: you know how to drive it, but not how the engine works

      Key Idea: Abstraction is about hiding the “how” and showing the “what”. It protects your data and makes code easier to use and maintain.

    • Encapsulation in Java: – A Complete Tutorial

      1. What is Encapsulation?

      Encapsulation is one of the four core OOP concepts (along with Inheritance, Polymorphism, and Abstraction).

      It is the mechanism of restricting direct access to some of an object’s components and providing controlled access through methods.

      Key points:

      • Data (fields) of a class are made private.
      • Access to these fields is provided via public getter and setter methods.
      • This helps control how the data is accessed or modified, improving security and maintainability.

      2. Why use Encapsulation?

      Encapsulation provides several benefits:

      1. Data Hiding – Prevents external classes from directly modifying sensitive data.
      2. Control Access – You can validate inputs before modifying a variable.
      3. Flexibility – Internal implementation can change without affecting external code.
      4. Improved Maintainability – Changes in one class do not affect others if encapsulation is properly used.

      3. How Encapsulation Works

      Encapsulation works by:

      1. Declaring class variables as private.
      2. Providing public getter and setter methods to access or modify these variables.
      3. Optionally, applying logic in setters to validate or restrict data.

      4. Example: Encapsulation in a POJO

      Here’s a simple example of a POJO with encapsulation:

      public class Student {
          // Step 1: Make fields private
          private String name;
          private int age;
      
          // Step 2: Provide public getters
          public String getName() {
              return name;
          }
      
          public int getAge() {
              return age;
          }
      
          // Step 3: Provide public setters with validation
          public void setName(String name) {
              if (name != null && name.length() > 0) {
                  this.name = name;
              } else {
                  System.out.println("Invalid name.");
              }
          }
      
          public void setAge(int age) {
              if (age > 0) {
                  this.age = age;
              } else {
                  System.out.println("Age must be positive.");
              }
          }
      }
      

      5. Using the Encapsulated Class

      public class Main {
          public static void main(String[] args) {
              Student student = new Student();
      
              // Trying to directly access the fields (won't work)
              // student.name = "John"; // ERROR: name has private access
      
              // Using setter methods
              student.setName("John");  // Allowed
              student.setAge(25);       // Allowed
              student.setAge(-5);       // Rejected due to validation
      
              // Using getter methods
              System.out.println("Student Name: " + student.getName());
              System.out.println("Student Age: " + student.getAge());
          }
      }
      

      Output:

      Age must be positive.
      Student Name: John
      Student Age: 25
      

      6. How it Restricts Data

      • Direct modification is blocked: private keyword prevents other classes from accessing the variables.
      • Controlled modification: The setter validates the data before setting it.
      • Read-only or write-only access: You can provide only getter (read-only) or setter (write-only) if needed.

      Example: Read-only field

      private final String studentId; // Cannot be changed once assigned
      
      public String getStudentId() {
          return studentId;
      }
      
      // No setter method, so it’s read-only
      

      7. Real-world analogy

      Think of encapsulation like a bank account:

      • Your balance is private.
      • You can’t just change it directly.
      • You deposit or withdraw through controlled methods.
      • The bank validates your transactions before updating your balance.