0% found this document useful (0 votes)
25 views50 pages

HCL RealTimeInterviewRound2

Uploaded by

tellapuri.naresh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views50 pages

HCL RealTimeInterviewRound2

Uploaded by

tellapuri.naresh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 50

The `distinct()` method in Java is used to **remove duplicate elements** from a

stream, ensuring that each element in the resulting stream is unique. It is part of
the **Stream API** introduced in **Java 8**.

When you invoke `distinct()` on a stream, it filters out repeated elements based on
their **natural equality** (using `equals()` method) and returns a new stream where
each element appears only once.

### Syntax:
```java
Stream<T> distinct();
```
- The `distinct()` method operates on a `Stream` and returns a new `Stream` that
contains only distinct elements.
- The underlying mechanism that determines distinctness is the **`equals()`**
method of the objects in the stream.

### Key Points:


- The order of elements in the resulting stream is preserved.
- The operation is **intermediate** (it returns a new stream), and it is **lazy**
(it only processes elements when the stream is consumed).
- If an element has already appeared in the stream, it will not be included again
in the resulting stream.

### Example of Using `distinct()` in Java:

Here’s an example that demonstrates how `distinct()` works with a stream:

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class DistinctExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 4, 5, 2, 1, 6, 7);

List<Integer> distinctNumbers = numbers.stream()


.distinct() // Remove duplicates
.collect(Collectors.toList());

System.out.println(distinctNumbers); // Output: [1, 2, 3, 4, 5, 6, 7]


}
}
```

### Explanation:
- We create a list of integers with some duplicate values.
- By calling `numbers.stream().distinct()`, we create a stream where only unique
elements remain.
- The `collect(Collectors.toList())` operation collects the results into a `List`,
and the final output contains the distinct elements.

### Example with Custom Objects:

If you're working with custom objects (e.g., `Person` objects), the `distinct()`
method uses the `equals()` method to determine uniqueness. So, if you want to use
`distinct()` with custom objects, you must override the `equals()` and `hashCode()`
methods to define what makes two objects equal.
```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Person {
String name;
int age;

Person(String name, int age) {


this.name = name;
this.age = age;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}

@Override
public String toString() {
return name + " (" + age + ")";
}
}

public class DistinctCustomObjectsExample {


public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Alice", 30), // Duplicate
new Person("Charlie", 35)
);

List<Person> distinctPeople = people.stream()


.distinct() // Remove duplicates
.collect(Collectors.toList());

System.out.println(distinctPeople); // Output: [Alice (30), Bob (25),


Charlie (35)]
}
}
```

### Explanation:
- We define a `Person` class with fields `name` and `age`.
- We override the `equals()` and `hashCode()` methods to ensure that two `Person`
objects are considered equal if they have the same `name` and `age`.
- By calling `distinct()` on the stream of `Person` objects, duplicates based on
`name` and `age` are removed.
### Performance Considerations:
- The `distinct()` operation internally uses a `HashSet` to track already
encountered elements, so it generally performs well, with a time complexity of
**O(n)** where **n** is the number of elements in the stream.
- However, you should be mindful of the performance cost of the `equals()` and
`hashCode()` methods for custom objects, as these operations will be invoked to
check for duplicates.

---

### Summary:
- The `distinct()` method in Java is used to eliminate duplicate elements from a
stream.
- It uses the `equals()` method to check for duplicates and retains only the unique
elements in the resulting stream.
- It's commonly used in scenarios where you want to filter out redundant data,
either for primitive values or custom objects (as long as `equals()` and
`hashCode()` are properly overridden).
----------------------------------------------------------------------
to filter List of duplicate elements either we can use Set Interface or we can go
with Stream Api distinct().
====================================================
In Java 8's **Stream API**, operations are classified into two types:

1. **Intermediate Operations**
2. **Terminal Operations**

These operations are used to process streams of data in a functional style. The key
difference between intermediate and terminal operations is that **intermediate
operations** are **lazy** and return a new stream, while **terminal operations**
are **eager** and consume the stream to produce a result (e.g., a collection, a
primitive value, etc.).

### 1. **Intermediate Operations**:


Intermediate operations are operations that transform a stream into another stream.
These operations are **lazy**, meaning they are not executed until a terminal
operation is invoked on the stream. They allow for building a pipeline of
operations that are executed when the stream is processed.

Intermediate operations are **non-blocking**, and they allow chaining multiple


operations in a sequence. Each intermediate operation returns a new stream, so you
can chain them together.

#### Common Intermediate Operations:


- **`map()`**: Transforms each element of the stream into a new element by applying
a function.
- **`filter()`**: Filters elements based on a condition (predicate).
- **`distinct()`**: Removes duplicate elements.
- **`sorted()`**: Sorts the elements of the stream.
- **`flatMap()`**: Flattens a stream of collections into a single stream.
- **`peek()`**: Allows you to see elements in the stream without modifying them
(mostly for debugging).
- **`limit()`**: Limits the stream to the first `n` elements.
- **`skip()`**: Skips the first `n` elements of the stream.

#### Example of Intermediate Operations:


```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class IntermediateOperationsExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> result = numbers.stream()


.filter(n -> n % 2 == 0) // Filter even
numbers
.map(n -> n * 2) // Multiply each
even number by 2
.distinct() // Remove
duplicates (if any)
.collect(Collectors.toList()); // Collect
result into a list

System.out.println(result); // Output: [4, 8, 12, 16, 20]


}
}
```

In this example:
- The `filter()` operation is an intermediate operation that filters out the odd
numbers.
- The `map()` operation transforms each even number by multiplying it by 2.
- The `distinct()` operation ensures that no duplicates are included.

### 2. **Terminal Operations**:


Terminal operations are operations that produce a result or a side-effect and
**consume** the stream. Once a terminal operation is invoked, the stream is
considered "consumed," and no further operations can be performed on it.

When a terminal operation is called, all intermediate operations are executed as


part of the stream processing.

#### Common Terminal Operations:


- **`collect()`**: Collects the elements of the stream into a container, such as a
list, set, or map.
- **`forEach()`**: Performs an action on each element of the stream.
- **`reduce()`**: Reduces the elements of the stream to a single value using a
binary operator (e.g., sum, product).
- **`count()`**: Counts the number of elements in the stream.
- **`anyMatch()`**: Checks if any element in the stream matches a given condition.
- **`allMatch()`**: Checks if all elements in the stream match a given condition.
- **`noneMatch()`**: Checks if no elements in the stream match a given condition.
- **`findFirst()`**: Returns the first element in the stream (if present).
- **`findAny()`**: Returns any element in the stream (typically used in parallel
streams).
- **`min()` / `max()`**: Returns the minimum or maximum element according to a
comparator.

#### Example of Terminal Operations:


```java
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class TerminalOperationsExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Terminal operation: forEach


System.out.println("Using forEach:");
numbers.stream()
.forEach(n -> System.out.println(n)); // Prints each element

// Terminal operation: count


long count = numbers.stream()
.filter(n -> n % 2 == 0) // Filter even numbers
.count(); // Counts the number of even numbers
System.out.println("Count of even numbers: " + count);

// Terminal operation: reduce (sum of numbers)


Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b); // Sums all
elements
sum.ifPresent(s -> System.out.println("Sum of all numbers: " + s));

// Terminal operation: findFirst


Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst(); // Find the first even
number
firstEven.ifPresent(n -> System.out.println("First even number: " + n));
}
}
```

### Explanation:
- **`forEach()`**: This terminal operation performs an action on each element. In
this case, it prints each number.
- **`count()`**: This terminal operation counts the number of elements in the
stream that match the filter condition.
- **`reduce()`**: This terminal operation is used to aggregate elements into a
single value (in this case, summing all the numbers).
- **`findFirst()`**: This terminal operation retrieves the first element of the
stream that matches the condition.

### Key Differences Between Intermediate and Terminal Operations:

| Aspect | Intermediate Operations | Terminal


Operations |
|----------------------------|-------------------------------------------|---------
----------------------------------|
| **Execution** | Lazy, executed only when terminal operation is
invoked | Eager, executed immediately when invoked |
| **Return Type** | Returns a new stream | Returns
a final result or side effect (e.g., `List`, `Optional`, `void`) |
| **Order of Execution** | Only executed when the stream is consumed |
Triggers the execution of the entire pipeline of intermediate operations |
| **Side Effects** | No side effects (purely functional) | May
have side effects (e.g., `forEach()`, `collect()`) |
| **Can Be Chained** | Yes, multiple intermediate operations can be
chained together | No, terminal operations consume the stream and stop further
processing |

### Example with Both Intermediate and Terminal Operations:


```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CombinedOperationsExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Intermediate operations followed by terminal operation


List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // Intermediate:
Filter even numbers
.map(n -> n * 2) // Intermediate:
Multiply each number by 2
.collect(Collectors.toList()); // Terminal:
Collect to List

System.out.println(result); // Output: [4, 8, 12, 16, 20]


}
}
```

### Summary:

- **Intermediate operations**: Operations like `map()`, `filter()`, `distinct()`,


`sorted()`, and `flatMap()` that transform streams. They are **lazy** and can be
chained.
- **Terminal operations**: Operations like `collect()`, `forEach()`, `reduce()`,
`count()`, `findFirst()` that produce a final result or side effect. They
**consume** the stream and trigger the execution of intermediate operations.

Intermediate operations allow you to build a processing pipeline, while terminal


operations trigger the processing and provide the result.
=======================================
### How `HashMap` Works Internally in Java

A `HashMap` in Java is a part of the `java.util` package and provides a basic


implementation of the `Map` interface. It stores key-value pairs, where each key is
unique, and the values can be duplicated. `HashMap` provides constant-time average
performance for basic operations like `get()`, `put()`, and `remove()`.

Internally, `HashMap` works based on a **hash table** and uses the **hashing**
mechanism to quickly locate keys and their associated values.

### Key Concepts of `HashMap` Internals:


1. **Buckets**: Internally, `HashMap` stores entries (key-value pairs) in an array
of "buckets". Each bucket corresponds to an index in the array.
2. **Hashing**: A hash function is applied to the key to compute an index in the
bucket array where the entry will be stored.
3. **Collision Handling**: If two keys have the same hash code and end up in the
same bucket, `HashMap` handles this "collision" by storing multiple entries in a
**linked list** or **balanced tree** within the same bucket (depending on the
number of entries in the bucket).
4. **Resize Mechanism**: As the map grows, `HashMap` resizes (doubles the size of
the bucket array) to maintain efficient lookup times.

Let’s break down the key components and how the `HashMap` functions internally:
---

### 1. **The Structure of a HashMap**


Internally, `HashMap` is backed by an array of `Node` objects (in older versions of
Java, it was backed by an array of `Entry` objects).

Each `Node` in the array contains:


- The **hash code** of the key.
- The **key** itself.
- The **value** associated with the key.
- A **next** pointer that links to the next node in case of collisions (a chain of
nodes).

In modern versions of Java (from Java 8 onwards), if a bucket contains many


elements (more than 8), a **balanced tree** (specifically a **Red-Black Tree**) is
used instead of a linked list for improved lookup and insertion performance.

---

### 2. **Put Operation (`put(K key, V value)`)**

When a key-value pair is added to the `HashMap` using the `put()` method, the
following steps are performed:

1. **Compute the Hash Code**:


- The hash code of the key is computed using the `hashCode()` method of the key
object.
- The hash code is then processed (e.g., via a hashing function) to map the key
to an index in the bucket array. This index is calculated as:
```java
index = hash(key.hashCode()) % bucketArraySize;
```

2. **Determine the Bucket**:


- The computed index is used to identify the bucket (which is simply an array
element) where the entry should be stored.

3. **Handle Collisions**:
- If the bucket is empty, the new `Node` (key-value pair) is inserted.
- If the bucket already contains other entries, a collision occurs:
- **Linked List**: In earlier versions of Java, a linked list is formed in the
bucket to hold all elements with the same hash value (this is a simple collision
resolution strategy).
- **Balanced Tree**: Starting from Java 8, if the number of elements in a
bucket exceeds a threshold (usually 8), a **Red-Black Tree** is used for improved
performance (O(log n) lookup time).

4. **Resize the Map**:


- If the number of entries in the `HashMap` exceeds the **load factor**
threshold (usually 75% of the current capacity), the `HashMap` resizes.
- This means the bucket array is expanded, and existing entries are rehashed and
redistributed across the new array. This process helps in maintaining the average
time complexity for operations.

---

### 3. **Get Operation (`get(K key)`)**

The `get()` operation retrieves the value associated with the provided key:
1. **Compute the Hash Code**:
- The hash code of the key is computed again using the `hashCode()` method of
the key.

2. **Locate the Bucket**:


- Using the computed hash code, the index of the bucket in the bucket array is
calculated.

3. **Traverse the Bucket**:


- If the bucket is empty, `null` is returned.
- If the bucket contains multiple nodes (due to collisions), the nodes are
traversed one by one, comparing the key of each node with the provided key.
- If a matching key is found, the corresponding value is returned.

4. **Balanced Tree (if used)**: If the bucket uses a balanced tree, the search time
is O(log n), which is better than the O(n) search time in a linked list.

---

### 4. **Remove Operation (`remove(Object key)`)**

The `remove()` method works similarly to `get()`, but instead of returning the
value, it removes the key-value pair:

1. **Compute the Hash Code**: The hash code of the key is computed.
2. **Locate the Bucket**: The bucket index is determined using the hash code.
3. **Traverse the Bucket**:
- If a matching key is found, the corresponding key-value pair is removed.
- If there are other nodes in the same bucket (due to collisions), the list/tree
is updated to remove the node.

---

### 5. **Rehashing and Resizing**

A `HashMap` has a capacity (number of buckets) and a **load factor** (default is


0.75), which determines when the map should resize.

- When the number of entries exceeds `capacity * loadFactor`, the `HashMap` is


resized (usually doubled). All existing entries are rehashed and redistributed
across the new, larger array.
- This process ensures that the `HashMap` maintains a balance between memory usage
and the time complexity of operations (`get()`, `put()`, `remove()`).

Rehashing is an expensive operation, so it's important to initialize the `HashMap`


with an appropriate initial capacity if you know the number of entries you'll
store.

---

### 6. **Hashing and Collision Resolution**

When two keys have the same hash code, they are considered "colliding" and end up
in the same bucket. This can reduce the performance of `get()` and `put()`
operations.

- **Separate Chaining (Linked List)**: In earlier versions of Java, collisions were


resolved by adding the new entry to the end of a linked list in the bucket. If
there are many collisions, searching through the list can take O(n) time.
- **Balanced Tree (Red-Black Tree)**: In newer versions of Java (from Java 8
onwards), if a bucket has more than 8 entries, it is converted into a Red-Black
Tree. Searching within a balanced tree takes O(log n) time, which improves
performance when there are many collisions.

---

### 7. **Key Points to Remember**


- **Hash Code**: `HashMap` relies heavily on the hash code of the keys to calculate
the index in the bucket array.
- **Performance**: The time complexity for basic operations like `put()`, `get()`,
and `remove()` is O(1) on average, but it can degrade to O(n) if there are a lot of
collisions and the bucket is implemented as a linked list.
- **Collision Handling**: `HashMap` handles collisions via linked lists or Red-
Black Trees, which helps maintain performance when many elements hash to the same
index.
- **Resizing**: When the load factor threshold is exceeded, the `HashMap` resizes
(doubles its capacity) and rehashes all entries to a new array.

---

### Example of Basic HashMap Usage:

```java
import java.util.HashMap;

public class HashMapExample {


public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();

// Put operation
map.put("A", "Apple");
map.put("B", "Banana");
map.put("C", "Cherry");

// Get operation
System.out.println(map.get("A")); // Output: Apple

// Remove operation
map.remove("B");

// Check if a key exists


System.out.println(map.containsKey("B")); // Output: false
}
}
```

### Conclusion:
- **HashMap** in Java works by using an array of "buckets" where each bucket is an
index in the array.
- A **hash function** computes an index based on the hash code of the key, and this
index determines where to store the key-value pair.
- Collisions are handled by either a **linked list** or a **Red-Black Tree** (in
newer versions of Java) to maintain performance.
- The map resizes when the load factor exceeds a threshold, which helps keep
operations efficient.
=========================
Sure! Here’s a **straightforward** explanation of why we need to override
`hashCode()` and `equals()` in Java:

### Why Override `hashCode()` and `equals()`?

1. **`hashCode()`**:
- It generates a unique number (hash) for an object, which helps hash-based
collections (like `HashMap`, `HashSet`) to quickly find objects.
- If we use custom objects as keys in a `HashMap` or elements in a `HashSet`, we
need to ensure that objects with the same "logical equality" (e.g., two people with
the same name and age) have the same hash code. If we don’t, the collection will
fail to find or compare them correctly.

2. **`equals()`**:
- It checks if two objects are "logically equal" based on their **content**
(e.g., if two `Person` objects have the same name and age).
- The default `equals()` compares objects by **reference** (memory address),
which means two objects with the same content may be considered "not equal." This
is not what you want when comparing custom objects for logical equality (e.g., two
`Person` objects with the same name and age).

### **The Relationship Between `hashCode()` and `equals()`**


- **`hashCode()` and `equals()` must work together**:
- If two objects are equal according to `equals()`, they **must** have the same
hash code. This ensures they end up in the correct bucket in collections like
`HashMap` or `HashSet`.
- If two objects have the same hash code but are not equal (based on `equals()`),
that's fine, but it’s important to handle collisions in the `hashMap`.

---

### Example

Let's say you have a `Person` class:

```java
public class Person {
private String name;
private int age;

// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}

// Overriding equals to compare objects based on name and age


@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}

// Overriding hashCode to ensure objects with same name and age have the same
hash code
@Override
public int hashCode() {
return 31 * name.hashCode() + age;
}
}
```

### Why We Overridden These Methods:


- **`equals()`**: We want two `Person` objects to be considered equal if they have
the same name and age.
- **`hashCode()`**: We want these equal `Person` objects to return the same hash
code so that they can be found correctly in hash-based collections like `HashMap`
and `HashSet`.

Without overriding these methods, if we used `Person` as a key in a `HashMap`, two


`Person` objects with the same name and age might be stored in different places
(buckets) due to different hash codes, which is incorrect behavior.

---

### Summary:
- **Override `equals()`**: To define how two objects are logically equal.
- **Override `hashCode()`**: To ensure that logically equal objects have the same
hash code.
- **Why?**: To ensure that hash-based collections (like `HashMap`, `HashSet`) work
correctly, as they rely on both methods to store and retrieve objects efficiently.

=======================
To create **immutable objects** in Java, you need to follow a few specific rules
and guidelines to ensure that the object's state cannot be changed after it is
created.

### **Steps to Create Immutable Objects:**

1. **Make the class `final`:**


- A `final` class cannot be subclassed, preventing other classes from modifying
its behavior.

```java
public final class Person {
// Fields, constructor, methods
}
```

2. **Make all fields `private` and `final`:**


- `private` ensures that fields cannot be directly accessed from outside the
class.
- `final` ensures that the field can be assigned only once, typically during
object creation, and its value cannot be changed afterward.

```java
public final class Person {
private final String name;
private final int age;
}
```

3. **Initialize fields via a constructor:**


- Use a constructor to set the values of the fields when the object is created.
Since the fields are `final`, they can only be assigned in the constructor.

```java
public final class Person {
private final String name;
private final int age;

public Person(String name, int age) {


this.name = name;
this.age = age;
}
}
```

4. **Do not provide "setter" methods:**


- Setter methods allow modification of fields. Since immutable objects cannot
change their state, you should not provide setters.

```java
public final class Person {
private final String name;
private final int age;

public Person(String name, int age) {


this.name = name;
this.age = age;
}

// No setters allowed
}
```

5. **Ensure fields are not mutable (if they are objects):**


- If a field is a reference to a mutable object (like an array or a `List`), you
must ensure that it cannot be modified by providing a **deep copy** of the object
in the constructor and returning a copy from the getter methods.

For example, if the class has a `List` or `Date` as a field, you should not
expose it directly; instead, return a **new copy** of the object or **unmodifiable
view** of the collection.

**Incorrect Approach (leaks mutability):**


```java
public class Person {
private final List<String> hobbies;

public Person(List<String> hobbies) {


this.hobbies = hobbies; // This can allow external modification!
}

public List<String> getHobbies() {


return hobbies; // Return the original reference
}
}
```

**Correct Approach (immutable reference):**


```java
import java.util.Collections;
import java.util.List;

public final class Person {


private final List<String> hobbies;

public Person(List<String> hobbies) {


this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
}

public List<String> getHobbies() {


return hobbies;
}
}
```

- In this example, we create an **unmodifiable copy** of the list in the


constructor to ensure the list cannot be modified later.

6. **Return copies (not references) of mutable fields:**


- If your immutable object holds references to mutable objects, return copies of
those objects, not the original reference. This prevents external modifications to
the object’s state.

Example with a `Date` field:


```java
import java.util.Date;

public final class Event {


private final Date eventDate;

public Event(Date eventDate) {


// Create a new Date object to avoid modification later
this.eventDate = new Date(eventDate.getTime());
}

public Date getEventDate() {


// Return a new copy to avoid changes to the internal state
return new Date(eventDate.getTime());
}
}
```

### **Full Example of an Immutable Object:**

Here's a complete example of an immutable class:

```java
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

public final class Person {


private final String name;
private final int age;
private final List<String> hobbies;

// Constructor to initialize the fields


public Person(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// Create an unmodifiable copy of the list
this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
}

// Getter for name (no setter)


public String getName() {
return name;
}

// Getter for age (no setter)


public int getAge() {
return age;
}

// Getter for hobbies (returns unmodifiable list)


public List<String> getHobbies() {
return hobbies;
}
}

```

### **Explanation of the Example:**

- **Class is `final`**: This prevents subclassing and keeps the class immutable.
- **Fields are `private` and `final`**: This ensures the fields are not directly
accessible or modifiable after the object is created.
- **No setter methods**: Since we don’t allow modification of the object’s state,
no setters are provided.
- **Deep copy for mutable fields**: The list of hobbies is copied and wrapped in an
unmodifiable collection to prevent external modification.

### **Why Immutable Objects are Useful:**

1. **Thread-Safety**: Immutable objects are inherently thread-safe because their


state cannot change after creation, so multiple threads can safely access them
without synchronization.

2. **HashMap Keys**: Immutable objects are safe to use as keys in hash-based


collections like `HashMap` or `HashSet` because their hash codes don’t change after
creation.

3. **Predictable Behavior**: Since their state can’t change, immutable objects are
easier to reason about and debug, especially in large applications.

### **Summary:**

To create an immutable object:


- Make the class `final`.
- Make fields `private` and `final`.
- Initialize fields via a constructor.
- Do not provide setters.
- Ensure fields that are mutable (e.g., `List`, `Date`) are copied or wrapped in
unmodifiable collections to prevent changes after the object is created.

Immutable objects are useful for ensuring thread-safety, reliability, and ease of
maintenance.
============================
### **Creational Design Patterns: Overview**

Creational design patterns are a category of design patterns that focus on **object
creation mechanisms**, trying to create objects in a manner suitable to the
situation. These patterns abstract the instantiation process, making it more
flexible and efficient.

The goal of creational patterns is to provide various ways to **create objects**,


ensuring that the creation process is controlled and can be adjusted based on the
system's needs.

### **Types of Creational Design Patterns**

There are five main creational design patterns in object-oriented design:

1. **Singleton**
2. **Factory Method**
3. **Abstract Factory**
4. **Builder**
5. **Prototype**

---

### **1. Singleton Pattern**

#### **Definition:**
The **Singleton Pattern** ensures that a class has only one instance, and provides
a global point of access to that instance.

#### **When to Use:**


- When you need exactly **one instance** of a class in your application (e.g., a
configuration manager or database connection pool).
- To control access to shared resources.

#### **Implementation Example:**

```java
public class Singleton {

// Step 1: Create a private static variable to hold the instance.


private static Singleton instance;

// Step 2: Make the constructor private so it cannot be instantiated from


outside.
private Singleton() {}

// Step 3: Provide a public method to get the instance.


public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

public void showMessage() {


System.out.println("Hello, this is a Singleton instance!");
}
}

public class Main {


public static void main(String[] args) {
// Access the Singleton instance and call its method
Singleton singleton = Singleton.getInstance();
singleton.showMessage();
}
}
```

#### **Explanation:**
- The `Singleton` class ensures only one instance is created.
- The `getInstance()` method is the key; it ensures that the instance is created
only when it's needed.
- The constructor is private, preventing instantiation from outside the class.

---

### **2. Factory Method Pattern**

#### **Definition:**
The **Factory Method Pattern** provides an interface for creating objects, but the
**subclasses** decide which class to instantiate. It allows subclasses to alter the
type of objects that will be created.

#### **When to Use:**


- When a class cannot anticipate the class of objects it must create.
- When you want to **delegate** the responsibility of object creation to subclasses
or client classes.

#### **Implementation Example:**

```java
// Product interface
interface Animal {
void speak();
}

// Concrete products
class Dog implements Animal {
public void speak() {
System.out.println("Woof!");
}
}

class Cat implements Animal {


public void speak() {
System.out.println("Meow!");
}
}

// Creator class
abstract class AnimalFactory {
public abstract Animal createAnimal();
}

// Concrete creator classes


class DogFactory extends AnimalFactory {
public Animal createAnimal() {
return new Dog();
}
}
class CatFactory extends AnimalFactory {
public Animal createAnimal() {
return new Cat();
}
}

public class Main {


public static void main(String[] args) {
AnimalFactory dogFactory = new DogFactory();
Animal dog = dogFactory.createAnimal();
dog.speak(); // Outputs: Woof!

AnimalFactory catFactory = new CatFactory();


Animal cat = catFactory.createAnimal();
cat.speak(); // Outputs: Meow!
}
}
```

#### **Explanation:**
- The `AnimalFactory` defines a **factory method** (`createAnimal`) which is
implemented by subclasses (`DogFactory`, `CatFactory`).
- This allows the creation of different products (`Dog`, `Cat`) without altering
the client code.

---

### **3. Abstract Factory Pattern**

#### **Definition:**
The **Abstract Factory Pattern** provides an interface for creating families of
related or dependent objects without specifying their concrete classes. It is
essentially a **factory of factories**.

#### **When to Use:**


- When the system needs to create **multiple families of related objects** (e.g.,
UI components for different platforms).
- When you want to **abstract** the creation logic and decouple the client code
from the concrete classes.

#### **Implementation Example:**

```java
// Abstract product types
interface Button {
void render();
}

interface Checkbox {
void render();
}

// Concrete product types for Windows


class WindowsButton implements Button {
public void render() {
System.out.println("Rendering Windows Button.");
}
}
class WindowsCheckbox implements Checkbox {
public void render() {
System.out.println("Rendering Windows Checkbox.");
}
}

// Concrete product types for Mac


class MacButton implements Button {
public void render() {
System.out.println("Rendering Mac Button.");
}
}

class MacCheckbox implements Checkbox {


public void render() {
System.out.println("Rendering Mac Checkbox.");
}
}

// Abstract Factory interface


interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}

// Concrete factories
class WindowsFactory implements GUIFactory {
public Button createButton() {
return new WindowsButton();
}

public Checkbox createCheckbox() {


return new WindowsCheckbox();
}
}

class MacFactory implements GUIFactory {


public Button createButton() {
return new MacButton();
}

public Checkbox createCheckbox() {


return new MacCheckbox();
}
}

public class Main {


public static void main(String[] args) {
GUIFactory windowsFactory = new WindowsFactory();
Button windowsButton = windowsFactory.createButton();
windowsButton.render(); // Rendering Windows Button
Checkbox windowsCheckbox = windowsFactory.createCheckbox();
windowsCheckbox.render(); // Rendering Windows Checkbox

GUIFactory macFactory = new MacFactory();


Button macButton = macFactory.createButton();
macButton.render(); // Rendering Mac Button
Checkbox macCheckbox = macFactory.createCheckbox();
macCheckbox.render(); // Rendering Mac Checkbox
}
}
```

#### **Explanation:**
- `GUIFactory` is an abstract factory that defines methods for creating `Button`
and `Checkbox`.
- Concrete factories (`WindowsFactory`, `MacFactory`) implement the creation
methods for specific platform UI components.
- The client code can switch between different families of UI components without
knowing the specific classes being used.

---

### **4. Builder Pattern**

#### **Definition:**
The **Builder Pattern** is used to construct a complex object step by step. The
builder pattern allows you to create a **product** using the same construction
process, but different configurations.

#### **When to Use:**


- When an object needs to be created with a lot of optional parts or complex
construction processes.
- When you need to separate the construction of a product from its representation.

#### **Implementation Example:**

```java
class Computer {
private String CPU;
private String RAM;
private String storage;
private String GPU;

// Getters and setters


public String getCPU() { return CPU; }
public String getRAM() { return RAM; }
public String getStorage() { return storage; }
public String getGPU() { return GPU; }

@Override
public String toString() {
return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", Storage=" + storage +
", GPU=" + GPU + "]";
}
}

// Builder class
class ComputerBuilder {
private String CPU;
private String RAM;
private String storage;
private String GPU;

public ComputerBuilder setCPU(String CPU) {


this.CPU = CPU;
return this;
}
public ComputerBuilder setRAM(String RAM) {
this.RAM = RAM;
return this;
}

public ComputerBuilder setStorage(String storage) {


this.storage = storage;
return this;
}

public ComputerBuilder setGPU(String GPU) {


this.GPU = GPU;
return this;
}

public Computer build() {


Computer computer = new Computer();
computer.CPU = this.CPU;
computer.RAM = this.RAM;
computer.storage = this.storage;
computer.GPU = this.GPU;
return computer;
}
}

public class Main {


public static void main(String[] args) {
Computer computer = new ComputerBuilder()
.setCPU("Intel i9")
.setRAM("32GB")
.setStorage("1TB SSD")
.setGPU("NVIDIA RTX 3080")
.build();

System.out.println(computer); // Outputs: Computer [CPU=Intel i9, RAM=32GB,


Storage=1TB SSD, GPU=NVIDIA RTX 3080]
}
}
```

#### **Explanation:**
- The `ComputerBuilder` class allows you to **set individual parts** of the
`Computer` object step by step.
- The builder provides a fluent interface, so you can chain method calls to build a
fully constructed object.

---

### **5. Prototype Pattern**

#### **Definition:**
The **Prototype Pattern** is used to create new objects by **copying** or
**cloning** an existing object (the prototype). This is useful when the creation of
new objects is costly and time-consuming.

#### **When to Use:**


- When the cost of creating a new instance is greater than cloning an existing one.
- When you need to create **exact copies** of an object without altering its
internal state.

#### **Implementation Example:**

```java
// Prototype interface
interface Cloneable {
Cloneable clone();
}

// Concrete Prototype
class PrototypeExample implements Cloneable {
private String name;
private int age;

public PrototypeExample(String name, int age) {


this.name = name;
this.age = age;
}

public void display() {


System.out.println("

Name: " + name + ", Age: " + age);


}

@Override
public Cloneable clone() {
return new PrototypeExample(this.name, this.age);
}
}

public class Main {


public static void main(String[] args) {
PrototypeExample original = new PrototypeExample("John", 30);
original.display();

// Clone the original object


PrototypeExample clone = (PrototypeExample) original.clone();
clone.display(); // Name: John, Age: 30
}
}
```

#### **Explanation:**
- The `PrototypeExample` class implements the `Cloneable` interface and provides
the `clone()` method.
- The `clone()` method creates a **new object** with the same properties as the
original object.

---

### **Summary of Creational Patterns:**

1. **Singleton**: Ensures a class has only one instance.


2. **Factory Method**: Allows subclasses to decide which class to instantiate.
3. **Abstract Factory**: Creates families of related objects without specifying
concrete classes.
4. **Builder**: Constructs complex objects step by step.
5. **Prototype**: Creates new objects by cloning existing ones.

These patterns provide flexible and reusable solutions to object creation, making
your code more adaptable and easier to maintain.
=============================
Throwing a custom exception in a REST API response in a Spring Boot application
involves several steps. The key goal is to handle exceptions in a way that is
informative and consistent across all the API responses.

### Steps to Throw and Handle a Custom Exception in REST API:

1. **Define a Custom Exception**


2. **Create an Exception Handler (Global or Controller-specific)**
3. **Format the Response with `@ResponseStatus` or a Custom `ResponseEntity`**
4. **Use the Exception in your Controller**

### Example Walkthrough:

#### **1. Define a Custom Exception**

First, create a custom exception class. This will represent the specific error
scenario in your application.

```java
package com.example.demo.exception;

public class CustomNotFoundException extends RuntimeException {

private String resourceName;


private String fieldName;
private Object fieldValue;

public CustomNotFoundException(String resourceName, String fieldName, Object


fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName,
fieldValue));
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}

public String getResourceName() {


return resourceName;
}

public String getFieldName() {


return fieldName;
}

public Object getFieldValue() {


return fieldValue;
}
}
```

This `CustomNotFoundException` takes the resource name, the field name, and the
value of the field that was not found. The message is constructed in the
constructor to make it dynamic based on these values.
---

#### **2. Create a Global Exception Handler**

You can use the `@ControllerAdvice` annotation to define a global exception handler
that will handle exceptions thrown in any controller.

```java
package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

// Handle CustomNotFoundException
@ExceptionHandler(CustomNotFoundException.class)
public ResponseEntity<ErrorResponse>
handleCustomNotFoundException(CustomNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
ex.getResourceName(),
ex.getFieldName(),
ex.getFieldValue()
);
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}

// Handle all other exceptions (fallback)


@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.getMessage(),
null,
null,
null
);
return new ResponseEntity<>(errorResponse,
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
```

Here, we are catching the `CustomNotFoundException` and converting it into a


structured JSON response.

The `ErrorResponse` class (which you will define next) will hold the structure of
the error message.

---

#### **3. Define an ErrorResponse Class**

The `ErrorResponse` class is a POJO (Plain Old Java Object) that holds the
structure of the error response. It will contain information such as the error
code, message, and other relevant details.

```java
package com.example.demo.exception;

public class ErrorResponse {

private int statusCode;


private String message;
private String resourceName;
private String fieldName;
private Object fieldValue;

public ErrorResponse(int statusCode, String message, String resourceName,


String fieldName, Object fieldValue) {
this.statusCode = statusCode;
this.message = message;
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}

// Getters and Setters

public int getStatusCode() {


return statusCode;
}

public void setStatusCode(int statusCode) {


this.statusCode = statusCode;
}

public String getMessage() {


return message;
}

public void setMessage(String message) {


this.message = message;
}

public String getResourceName() {


return resourceName;
}

public void setResourceName(String resourceName) {


this.resourceName = resourceName;
}

public String getFieldName() {


return fieldName;
}

public void setFieldName(String fieldName) {


this.fieldName = fieldName;
}

public Object getFieldValue() {


return fieldValue;
}

public void setFieldValue(Object fieldValue) {


this.fieldValue = fieldValue;
}
}
```

This class is used to structure the response returned to the client when an
exception occurs.

---

#### **4. Use the Custom Exception in a Controller**

Now that you have your custom exception and handler in place, you can throw the
`CustomNotFoundException` in your controller whenever necessary.

```java
package com.example.demo.controller;

import com.example.demo.exception.CustomNotFoundException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

@GetMapping("/products/{id}")
public String getProductById(@PathVariable String id) {
// Simulating an invalid product ID scenario
if ("123".equals(id)) {
return "Product found";
} else {
throw new CustomNotFoundException("Product", "id", id);
}
}
}
```

In this example, the `getProductById` method checks if the product ID is `"123"`.


If it's not, it throws a `CustomNotFoundException`.

---

### **Sample API Responses**

**Successful Response:**

For a valid request, the response could be something like this (assuming `id=123`):

```json
{
"id": "123",
"name": "Product A",
"price": 99.99
}
```
**Error Response (404 Not Found):**

For an invalid request, such as `id=456`, where no product is found, the response
would look like:

```json
{
"statusCode": 404,
"message": "Product not found with id : '456'",
"resourceName": "Product",
"fieldName": "id",
"fieldValue": "456"
}
```

---

### **Why Is It Important to Use Custom Exceptions?**

- **Clearer Error Handling**: Custom exceptions provide a way to signal specific


error conditions (e.g., "Not Found", "Bad Request") in a more descriptive manner,
which helps you to handle errors more effectively.

- **Better Error Responses**: With custom exceptions, you can structure your error
responses in a way that the client can easily understand and react to, for example,
showing a user-friendly error message in the UI or logging it for analysis.

- **Centralized Handling**: By using a global exception handler


(`@ControllerAdvice`), you centralize error handling logic, making the code cleaner
and easier to maintain.

- **Scalability**: As your application grows, custom exceptions provide an easy way


to define and manage more specific error scenarios, without cluttering the code
with `try-catch` blocks everywhere.

---

### **Summary**

1. **Define a custom exception class** to represent specific error scenarios.


2. **Handle exceptions globally** using `@ControllerAdvice` and
`@ExceptionHandler`.
3. **Format the response** with a structured `ErrorResponse` that includes error
details.
4. **Use the custom exception** in your controllers to throw meaningful error
responses.
================================
### **Factory Design Pattern**

The **Factory Design Pattern** is a **creational design pattern** used to create


objects without specifying the exact class of object that will be created. It
provides a way to delegate the instantiation logic to a factory method, which
creates and returns an object. This pattern is useful when you need to create
objects based on certain conditions or configurations, but don't want the client
code to know the details of the object creation.

### **Why Use the Factory Design Pattern?**


There are several reasons why you might use the Factory Design Pattern:

### 1. **Encapsulation of Object Creation**


- The Factory pattern encapsulates the logic of creating objects. This helps to
separate the process of creating an object from its usage, making the code more
flexible and easier to maintain.
- If the object creation logic changes, you only need to change it in one place
(the factory), rather than in multiple places where objects are created.

### 2. **Decoupling Client Code from Object Creation**


- The client code does not need to know the details of how to instantiate
objects. This decouples the creation of objects from the rest of the system.
- Instead of using `new` to create an object, the client uses the factory method
to get the object. This promotes **loose coupling** between the client code and the
concrete classes.

### 3. **Managing Complex Object Creation**


- Sometimes, object creation involves complex logic. A factory method can
centralize this complexity in one place.
- For example, the factory can decide which class to instantiate based on
certain conditions or parameters.

### 4. **Flexibility in Object Creation**


- If your system needs to create different types of objects based on certain
parameters, the Factory pattern allows you to vary the object creation process
without changing the client code.
- It allows you to introduce new product types without affecting existing code
that relies on the factory.

### 5. **Enforcing a Common Interface**


- Factory methods can return objects that implement a common interface. This
makes the rest of the system interact with the objects through the interface,
without worrying about their specific implementations.
- This improves **polymorphism** and enables easier substitution of classes.

### 6. **Simplifies Code Maintenance**


- If object creation needs to be modified (e.g., changing the concrete class
used or the constructor parameters), you only need to modify the factory, not the
client code.
- This reduces the chance of errors in the system and keeps the codebase cleaner
and more maintainable.

### 7. **Supports Subclassing**


- You can have a base `Factory` class with subclasses that implement the logic
for creating specific types of objects. This allows for **extensibility** without
modifying the client code.

### **Real-World Example of Factory Pattern**

Consider a scenario where you are developing a **payment gateway** system.


Different payment methods (e.g., Credit Card, PayPal, Bitcoin) need to be
supported, and each payment method requires different logic for processing
transactions.

Without a factory pattern, the client would need to directly instantiate the
appropriate payment method class. This can quickly become messy and difficult to
maintain as the number of payment methods grows.

### Using Factory Pattern:


```java
// Payment interface
public interface Payment {
void processPayment();
}

// Concrete classes for different payment methods


public class CreditCardPayment implements Payment {
public void processPayment() {
System.out.println("Processing Credit Card Payment");
}
}

public class PayPalPayment implements Payment {


public void processPayment() {
System.out.println("Processing PayPal Payment");
}
}

public class BitcoinPayment implements Payment {


public void processPayment() {
System.out.println("Processing Bitcoin Payment");
}
}

// PaymentFactory class
public class PaymentFactory {
public static Payment getPaymentMethod(String type) {
if ("CreditCard".equalsIgnoreCase(type)) {
return new CreditCardPayment();
} else if ("PayPal".equalsIgnoreCase(type)) {
return new PayPalPayment();
} else if ("Bitcoin".equalsIgnoreCase(type)) {
return new BitcoinPayment();
}
throw new IllegalArgumentException("Unknown payment type");
}
}

// Client code
public class PaymentProcessor {
public static void main(String[] args) {
Payment payment = PaymentFactory.getPaymentMethod("PayPal");
payment.processPayment(); // Output: Processing PayPal Payment
}
}
```

### **In the Example Above**:


- **`Payment`** is the interface that all payment methods implement.
- **Concrete classes** (`CreditCardPayment`, `PayPalPayment`, `BitcoinPayment`)
implement the `Payment` interface.
- **`PaymentFactory`** provides a static method to instantiate the appropriate
concrete class based on the payment type.
- **Client code** (`PaymentProcessor`) simply calls the factory method and does not
need to know the details of which concrete class is being instantiated. It just
works with the `Payment` interface.
### **Benefits of the Factory Pattern in This Example**:
- **Encapsulation of Object Creation**: The factory encapsulates the complexity of
creating the appropriate payment object. The client does not need to know how to
instantiate each payment method.
- **Decoupling**: The client does not need to know the concrete class (e.g.,
`CreditCardPayment`, `PayPalPayment`, etc.). It only relies on the `Payment`
interface.
- **Flexibility**: If new payment methods are introduced (e.g., Google Pay), you
just add a new class and modify the factory, without changing the client code.

### **Types of Factory Patterns**


1. **Simple Factory**: A single factory class that creates different types of
objects based on input.
2. **Factory Method**: A method in a class that is responsible for creating an
object (used in more complex scenarios).
3. **Abstract Factory**: A factory that returns a set of related objects. Useful
when you have families of related products (e.g., different UI components for
Windows or MacOS).

### **When to Use the Factory Pattern**


- When object creation is complex or involves many steps.
- When you need to decouple the client code from the concrete classes.
- When you want to add new types of products without modifying the existing client
code.
- When you want to manage the instantiation of objects in a central place.

### **Conclusion**
The **Factory Design Pattern** is used to **simplify object creation** and make
your code more flexible and maintainable by decoupling object creation from the
client code. It helps manage complex object creation and ensures that clients don’t
need to know about specific concrete classes. Instead, they rely on the factory to
create objects, thus promoting loose coupling and greater maintainability in the
system.
=============================
In Spring, **`RestTemplate`** was the primary client for making HTTP requests to
RESTful web services. However, since **Spring 5**, `RestTemplate` has been
**deprecated** in favor of a more modern and reactive alternative — **WebClient**.

Here’s a comparison of **`RestTemplate`** and **`WebClient`**:

### 1. **RestTemplate** (Deprecated, but still widely used)


- `RestTemplate` is a synchronous, blocking HTTP client used for making HTTP
requests to RESTful web services.
- It was the go-to choice in earlier versions of Spring, and it still works fine
for most synchronous use cases.

Example of using `RestTemplate`:

```java
import org.springframework.web.client.RestTemplate;
import org.springframework.http.ResponseEntity;

public class RestTemplateExample {


public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
String url = "https://api.example.com/data";

// Simple GET request


ResponseEntity<String> response = restTemplate.getForEntity(url,
String.class);
System.out.println(response.getBody());
}
}
```

- **Synchronous**: `RestTemplate` operates in a blocking manner, meaning it


waits for the response before continuing the execution.
- **Ease of Use**: Very simple to use for straightforward HTTP operations (GET,
POST, PUT, DELETE).
- **Deprecated**: As of Spring 5, `RestTemplate` is deprecated in favor of
`WebClient`. Spring recommends using `WebClient` for new development.

---

### 2. **WebClient** (Recommended, Modern, Reactive)


- `WebClient` is a more powerful and flexible non-blocking, reactive web client
introduced in **Spring WebFlux** in Spring 5.
- It supports **synchronous** and **asynchronous** operations, making it more
suited for modern applications, particularly those that require reactive
programming or high concurrency.

Example of using `WebClient`:

```java
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

public class WebClientExample {


public static void main(String[] args) {
WebClient webClient = WebClient.create("https://api.example.com");

// Making a GET request asynchronously


Mono<String> result = webClient.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class);

result.doOnTerminate(() -> System.out.println("Request complete"))


.subscribe(response -> System.out.println("Response: " +
response));
}
}
```

- **Non-blocking and Reactive**: `WebClient` supports asynchronous and non-


blocking calls, which makes it suitable for modern applications that need to handle
many concurrent requests efficiently.
- **Synchronous Support**: You can still use `WebClient` in a blocking
(synchronous) mode, similar to `RestTemplate`, for simpler use cases.
- **Highly Configurable**: More advanced features like retries, logging, custom
interceptors, and handling large file uploads/downloads are easier to implement.
- **WebFlux Integration**: It integrates seamlessly with **Spring WebFlux** for
reactive programming but can also be used in traditional Spring MVC applications.

---

### 3. **Other Alternatives to RestTemplate**


While `WebClient` is the most common modern alternative, depending on your use case
and preferences, there are other libraries or tools that can serve as alternatives
to `RestTemplate` for making HTTP requests:

- **Apache HttpClient**: A popular low-level HTTP client library, which is flexible


and provides more fine-grained control over HTTP communication.

Example of using `HttpClient`:


```java
import org.apache.http.impl.client.HttpClients;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.impl.client.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequestBase;

public class HttpClientExample {


public static void main(String[] args) {
CloseableHttpClient client = HttpClients.createDefault();
HttpUriRequestBase request = new HttpGet("https://api.example.com/data");

try (CloseableHttpResponse response = client.execute(request)) {


System.out.println(response.getStatusLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
```

- **OkHttp**: A modern and efficient HTTP client for Java, which is often used for
its ease of use and low overhead. It also supports synchronous and asynchronous
calls.

Example of using `OkHttp`:


```java
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class OkHttpExample {


public static void main(String[] args) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();

try (Response response = client.newCall(request).execute()) {


System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
```

- **Feign**: Feign is a declarative HTTP client developed by Netflix, which


simplifies HTTP requests. It integrates with Spring Cloud for microservices
communication, especially in Spring Cloud-based architectures.
Example of using Feign:
```java
@FeignClient(name = "example-api")
public interface ExampleApiClient {
@GetMapping("/data")
String getData();
}
```

- **RestAssured**: A powerful library mainly used for testing REST APIs. It


simplifies the process of sending requests and verifying responses in tests.

---

### **Which One to Choose?**

- **For New Projects**: **`WebClient`** is the recommended choice, especially if


you're using Spring 5 or later. It supports both synchronous and asynchronous
operations, integrates well with reactive programming, and is more powerful and
flexible than `RestTemplate`.

- **For Legacy Code**: If you already have code using `RestTemplate` and are not
yet using reactive programming, you may continue using `RestTemplate` for the time
being, but consider migrating to `WebClient` when possible.

- **For Simplicity**: If you need a simple HTTP client and are not using Spring,
libraries like **OkHttp** or **Apache HttpClient** can also be great options.

- **For Declarative API Calls**: If you are working within the **Spring Cloud**
ecosystem, **Feign** is a good option as it allows you to declare API clients in a
more declarative manner.

---

### Conclusion

In summary:
- **`RestTemplate`** is still widely used but is now deprecated in favor of
**`WebClient`**.
- **`WebClient`** is a more modern, flexible, and non-blocking alternative to
`RestTemplate`, and it's the preferred choice for new applications, especially if
you're working with reactive programming.
- **Other options** like **OkHttp**, **Apache HttpClient**, and **Feign** are also
available depending on your use case, but **WebClient** is recommended for Spring
applications.
===================================
In **`RestTemplate`**, HTTP headers are often used to provide additional
information in the request, such as authentication credentials, content types, and
custom headers. You can set headers using the **`HttpHeaders`** class and then use
them in an **`HttpEntity`**, which is passed along with the request to the server.

Here is an example of how to use headers in **`RestTemplate`**.

### Steps to Use Headers in `RestTemplate`:


1. Create an `HttpHeaders` object and add headers to it.
2. Wrap the headers in an `HttpEntity`.
3. Make the request using `RestTemplate` by passing the `HttpEntity` along with the
URL.
### Example 1: **GET Request with Headers**

Here’s an example of how to send a `GET` request with custom headers using
`RestTemplate`.

```java
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class RestTemplateExample {


public static void main(String[] args) {
// Create a RestTemplate instance
RestTemplate restTemplate = new RestTemplate();

// Create an HttpHeaders object and add custom headers


HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer your_token_here"); // Add
Authorization token
headers.set("Accept", "application/json"); // Specify that we expect JSON
responses

// Wrap headers in an HttpEntity


HttpEntity<String> entity = new HttpEntity<>(headers);

// Send a GET request with headers


String url = "https://api.example.com/data"; // Replace with your API
endpoint
ResponseEntity<String> response = restTemplate.exchange(url,
org.springframework.http.HttpMethod.GET, entity, String.class);

// Print the response body


System.out.println(response.getBody());
}
}
```

### Explanation of the Example:


1. **HttpHeaders**: This is where you add custom headers. In the example above, we
set an `Authorization` header with a Bearer token and an `Accept` header specifying
that we expect JSON as the response format.
2. **HttpEntity**: This wraps the headers (and optionally a request body, although
we don't need one for a GET request) and is passed along with the request.
3. **RestTemplate.exchange()**: The `exchange()` method is more flexible than the
simpler `getForObject()` or `postForObject()` methods. It allows you to specify the
HTTP method, request entity (including headers), and the response type.

### Example 2: **POST Request with Headers and Body**

Here’s an example of how to send a `POST` request with headers and a request body
using `RestTemplate`.

```java
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class RestTemplatePostExample {
public static void main(String[] args) {
// Create a RestTemplate instance
RestTemplate restTemplate = new RestTemplate();

// Create HttpHeaders and add custom headers


HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer your_token_here"); // Add
Authorization token
headers.set("Content-Type", "application/json"); // Set the Content-Type

// Sample JSON data as a body for the POST request


String jsonBody = "{\"name\": \"John\", \"age\": 30}";

// Wrap headers and body in an HttpEntity


HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers);

// Send a POST request with headers and body


String url = "https://api.example.com/users"; // Replace with your API
endpoint
ResponseEntity<String> response = restTemplate.exchange(url,
HttpMethod.POST, entity, String.class);

// Print the response body


System.out.println(response.getBody());
}
}
```

### Explanation of the POST Request:


- **HttpHeaders**: We set the `Authorization` header with a Bearer token and the
`Content-Type` to `application/json` since we are sending a JSON body in the
request.
- **HttpEntity**: Contains both the headers and the body. This is passed as part of
the `exchange()` method.
- **exchange()**: Performs a `POST` request, passing the `HttpEntity` containing
the request body and headers.

### Example 3: **Adding Multiple Headers**

You can also add multiple headers in the `HttpHeaders` object. Here’s an example
with multiple headers:

```java
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.web.client.RestTemplate;

public class RestTemplateMultipleHeadersExample {


public static void main(String[] args) {
// Create a RestTemplate instance
RestTemplate restTemplate = new RestTemplate();

// Create HttpHeaders and add custom headers


HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer your_token_here");
headers.set("Accept", "application/json");
headers.set("User-Agent", "MyApp/1.0");
headers.set("X-Custom-Header", "CustomValue");

// Wrap headers in an HttpEntity (no body in this case)


HttpEntity<String> entity = new HttpEntity<>(headers);

// Send GET request with multiple headers


String url = "https://api.example.com/data";
String response = restTemplate.exchange(url,
org.springframework.http.HttpMethod.GET, entity, String.class)
.getBody();

// Print the response body


System.out.println(response);
}
}
```

In this example, we've added several custom headers:


- `Authorization`: The token required to access the API.
- `Accept`: To specify that we expect a JSON response.
- `User-Agent`: To specify the client application name and version.
- `X-Custom-Header`: An example of a custom header.

### Key Points:


- **Headers** are added using the `HttpHeaders` class, and then wrapped inside an
`HttpEntity` which is passed along with the request.
- **HttpEntity** can contain both headers and a body. It is passed as a parameter
when calling methods like `exchange()`.
- **RestTemplate.exchange()** is more flexible than `getForObject()` or
`postForObject()` as it allows you to specify both headers and the body.

### Other Methods:


- **getForObject()**: Used for simple GET requests (without headers or body).
- **postForObject()**: Used for simple POST requests (without headers or body).

For more complex scenarios (especially with headers, authentication, or custom


behavior), using **`exchange()`** is preferred.

---

### Conclusion:
To summarize, you can set HTTP headers in **`RestTemplate`** by creating an
`HttpHeaders` object, wrapping it inside an `HttpEntity`, and passing the
`HttpEntity` to methods like `exchange()`. This provides flexibility when working
with APIs that require custom headers such as authentication tokens or content
types.
=======================================
The **`filter()`** method in Java's Stream API is an intermediate operation that
allows you to filter elements from a stream based on a given predicate (a
condition). It produces a new stream that contains only the elements that satisfy
the condition specified in the predicate.

### **Syntax:**
```java
Stream<T> filter(Predicate<? super T> predicate);
```

- **`T`**: The type of the elements in the stream.


- **`predicate`**: A **`Predicate`** is a functional interface that takes an
element of type `T` and returns a `boolean` (true or false). If the predicate
returns `true` for an element, that element will be included in the resulting
stream.

### **How `filter()` Works:**


- **Input**: It takes a predicate that defines the condition for filtering
elements.
- **Output**: It returns a new stream that contains only the elements that satisfy
the condition in the predicate.
- **Lazy operation**: The filtering is done lazily, meaning the actual filtering
happens when the terminal operation (like `collect()`, `forEach()`, etc.) is
invoked.

### **Example 1: Basic Example**


Let’s look at a simple example of using `filter()` to filter out even numbers from
a stream of integers.

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FilterExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Filter even numbers


List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());

System.out.println(evenNumbers); // Output: [2, 4, 6, 8, 10]


}
}
```

### **Explanation:**
- We create a stream from the list `numbers`.
- We use the `filter()` method with the predicate `n -> n % 2 == 0` to select only
the even numbers.
- The filtered elements are then collected into a new list using the `collect()`
method with `Collectors.toList()`.

### **Example 2: Filtering Strings**


You can also use `filter()` with streams of strings. Here’s an example that filters
out words that are longer than 4 characters.

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FilterStringsExample {


public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "kiwi", "pear",
"grape");

// Filter words that have more than 4 characters


List<String> filteredWords = words.stream()
.filter(word -> word.length() > 4)
.collect(Collectors.toList());

System.out.println(filteredWords); // Output: [apple, banana]


}
}
```

### **Explanation:**
- The stream is created from a list of strings.
- We use the `filter()` method with the predicate `word -> word.length() > 4` to
select only the words that have more than 4 characters.
- The filtered list is collected using `Collectors.toList()`.

### **Example 3: Combining Filters**


You can combine multiple `filter()` calls to apply more than one condition. Here’s
an example of filtering numbers that are both even and greater than 5.

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CombinedFilterExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Filter even numbers greater than 5


List<Integer> filteredNumbers = numbers.stream()
.filter(n -> n % 2 == 0) // Even
numbers
.filter(n -> n > 5) //
Greater than 5
.collect(Collectors.toList());

System.out.println(filteredNumbers); // Output: [6, 8, 10]


}
}
```

### **Explanation:**
- We first filter for even numbers (`n -> n % 2 == 0`).
- Then, we further filter the even numbers to get only those greater than 5 (`n ->
n > 5`).
- The resulting list contains `[6, 8, 10]`.

### **Key Points About `filter()`**


1. **Intermediate Operation**: `filter()` is an intermediate operation, meaning it
returns a new stream and does not modify the original stream.
2. **Predicate**: The filtering condition is provided by a `Predicate` function,
which returns `true` for elements that should be included in the resulting stream.
3. **Lazy Evaluation**: The filtering is lazy, meaning the filter operation is only
executed when a terminal operation (like `collect()`, `forEach()`, etc.) is
invoked.
4. **Does Not Change Original Stream**: The original stream is not modified; a new
stream is returned after applying the filter.

### **Performance Considerations:**


- **Lazy Execution**: Since `filter()` is a lazy operation, it can help with
performance when used with other stream operations. For instance, it doesn't
perform filtering until a terminal operation triggers the processing.

- **Parallel Streams**: You can combine `filter()` with parallel streams to


potentially improve performance for large datasets. For example:

```java
List<Integer> filteredNumbers = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
```

This will filter the even numbers in parallel, which might improve performance in
multi-core processors.

---

### **Conclusion:**
The `filter()` method in Java Streams is used to select elements that match a
specific condition, making it a powerful tool for working with collections and
streams. It provides a declarative way of expressing filtering logic and is
commonly used in data transformation and querying tasks.
=============================================
The `map()` method in Java Streams is an **intermediate operation** that is used to
transform each element of the stream into another form. It takes a **Function** (a
function that applies a transformation) as an argument and applies that function to
each element of the stream, producing a new stream of transformed elements.

### **Syntax of `map()` Method:**


```java
Stream<R> map(Function<? super T, ? extends R> mapper);
```

- **`T`**: The type of elements in the input stream.


- **`R`**: The type of elements in the output stream (after transformation).
- **`mapper`**: A function that takes an element of type `T` and transforms it to
an element of type `R`.

### **How `map()` Works:**


1. The `map()` function takes each element from the stream, applies the given
function to it, and produces a new stream.
2. It transforms the stream into a new form by applying the function (e.g., mapping
integers to strings, converting objects, applying mathematical transformations,
etc.).
3. The `map()` method is **lazy**, meaning the transformation does not occur until
a terminal operation like `collect()`, `forEach()`, or `reduce()` is triggered.

### **Example 1: Mapping from One Type to Another (e.g., Integer to String)**

Let’s say you have a list of integers, and you want to convert each integer to a
string.

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MapExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Using map to convert integers to strings


List<String> stringNumbers = numbers.stream()
.map(String::valueOf)
.collect(Collectors.toList());

System.out.println(stringNumbers); // Output: [1, 2, 3, 4, 5]


}
}
```

### **Explanation:**
- The stream of integers is transformed into a stream of strings using the `map()`
method.
- We use `String::valueOf` as the function to convert each integer to a string.
- The transformed stream is collected into a list of strings using `collect()`.

### **Example 2: Mapping with Custom Transformation (e.g., Squaring Numbers)**

In this example, we will use `map()` to transform a stream of integers into a


stream of their squares.

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SquareNumbersExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Using map to square each number


List<Integer> squaredNumbers = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());

System.out.println(squaredNumbers); // Output: [1, 4, 9, 16, 25]


}
}
```

### **Explanation:**
- We apply the transformation `n -> n * n` to square each number in the stream.
- The resulting stream of squared numbers is collected into a list.

### **Example 3: Mapping Objects (e.g., Extracting a Property from Objects)**

Suppose we have a list of `Person` objects, and we want to extract the names of all
persons into a list of strings.

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {


return name;
}
}

public class MapObjectsExample {


public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);

// Using map to extract names from Person objects


List<String> names = people.stream()
.map(Person::getName)
.collect(Collectors.toList());

System.out.println(names); // Output: [Alice, Bob, Charlie]


}
}
```

### **Explanation:**
- We have a stream of `Person` objects.
- The `map()` method extracts the `name` property from each `Person` object using
`Person::getName`.
- The transformed stream of names is collected into a list.

### **Example 4: Chaining Multiple `map()` Calls**

You can chain multiple `map()` calls to apply multiple transformations in sequence.

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MultipleMapExample {


public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "kiwi");

// Convert each word to uppercase and then find the length of each word
List<Integer> wordLengths = words.stream()
.map(String::toUpperCase) // Convert
to uppercase
.map(String::length) // Get
length of each word
.collect(Collectors.toList());

System.out.println(wordLengths); // Output: [5, 6, 4]


}
}
```

### **Explanation:**
- First, we apply the transformation `String::toUpperCase` to each word.
- Then, we apply `String::length` to each word to get its length.
- The resulting stream of word lengths is collected into a list.

### **Key Points About `map()`**


- **Intermediate Operation**: `map()` is an intermediate operation, which means it
returns a new stream and does not modify the original stream.
- **Transformation**: It is used to transform elements in a stream. The
transformation is done using the provided function, and the result is a new stream.
- **Lazy Execution**: Like other stream operations, `map()` is lazy and will not
process elements until a terminal operation (such as `collect()`, `forEach()`,
etc.) is invoked.
- **Function**: It takes a `Function` (a functional interface) that defines the
transformation for each element of the stream.
- **Chaining**: You can chain multiple `map()` calls to apply multiple
transformations in sequence.

### **Performance Considerations:**


- Since `map()` is an intermediate operation, it allows for the lazy and efficient
transformation of stream elements.
- Using `map()` in conjunction with **parallel streams** can improve performance
for large datasets because the transformation can happen in parallel across
multiple cores.

---

### **Conclusion:**
The `map()` method in Java Streams is a powerful way to transform data in a stream
from one type to another or to modify the elements. It is commonly used for tasks
such as converting elements to another type, extracting specific properties from
objects, or applying transformations like mathematical operations or string
manipulations.
===============================
The issue in the code is that you are calling `Integer::max` inside the `map()`
function, which is not correct because `Integer::max` is a **static method** that
requires two arguments, but `map()` expects a function that operates on a single
argument. This will result in a compilation error.

If you want to get the maximum value of each element in the stream, you should
apply a different approach. Here's how you can correct and improve your code:

### Corrected Code:

If the goal is to filter numbers greater than 100 and then map them to their
**maximum value** (relative to a given number), you would need to use `map()`
correctly. For example, if you want to map each number to its maximum with some
other value, you can do something like `map(n -> Integer.max(n, 100))`.

However, if the intent is to simply filter numbers greater than 100, here's a
simplified and corrected version:

### **1. Correct Filtering and Mapping Logic:**


```java
package com.InterviewPrep;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamapiMapExample {


public static void main(String[] args) {
List<Integer> list = Arrays.asList(89, 90, 112, 134, 234);

// Filter numbers greater than 100 and then map them to their maximum value
compared to 100
List<Integer> max = list.stream()
.filter(n -> n > 100) // Filter
numbers greater than 100
.map(n -> Integer.max(n, 100)) // Apply the
Integer.max transformation
.collect(Collectors.toList()); // Collect the
result into a list

System.out.println(max); // Output: [112, 134, 234]


}
}
```

### **Explanation:**
- **Filtering:** The `filter(n -> n > 100)` step filters out numbers less than or
equal to 100.
- **Mapping:** The `map(n -> Integer.max(n, 100))` step maps the remaining numbers
to their maximum value between the number and 100. This is redundant in this case,
because we already filtered numbers greater than 100, but it demonstrates how to
use `Integer::max`.
- **Collecting:** Finally, the resulting stream is collected into a list using
`.collect(Collectors.toList())`.

If you want to **just filter numbers greater than 100**, then you can omit the
`map()` step and just use:

```java
List<Integer> max = list.stream()
.filter(n -> n > 100)
.collect(Collectors.toList());
```

This will produce the list `[112, 134, 234]` without applying the `Integer.max`
transformation.

### **Summary:**
- The issue in the original code was trying to use `Integer::max` in a way that
wasn't compatible with `map()`. `Integer::max` is a binary method, and `map()`
requires a function that works on a single argument.
- The solution involves using `Integer.max` correctly with a lambda expression or
directly collecting the filtered values.
=========================
Ah, I see! If your goal is to first filter out numbers greater than 100 and then
find the **maximum** value among those numbers, you can do that easily using the
**`filter()`** method to first filter the numbers, and then use the **`max()`**
method from the **`Stream`** API to get the maximum number from the filtered
results.

To do this, you can use the `max()` method, which is a terminal operation, and it
requires a **comparator** to compare the elements.
Here’s how you can do it:

### **Solution:**
```java
package com.InterviewPrep;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class StreamapiMapExample {


public static void main(String[] args) {
List<Integer> list = Arrays.asList(89, 90, 112, 134, 234);

// Filter numbers greater than 100 and then find the maximum number
Optional<Integer> max = list.stream()
.filter(n -> n > 100) // Filter
numbers greater than 100
.max(Integer::compareTo); // Get the
maximum number

// Print the result, using Optional's ifPresent to handle empty stream


max.ifPresent(System.out::println); // Output: 234
}
}
```

### **Explanation:**
1. **`filter(n -> n > 100)`**: This filters out the numbers in the list that are
**greater than 100**.
2. **`max(Integer::compareTo)`**: This finds the **maximum** value in the filtered
stream by comparing the numbers. `Integer::compareTo` is a method reference that
compares two integers.
- The `max()` method returns an `Optional<Integer>`, because the stream may be
empty (for example, if there are no elements greater than 100).
3. **`ifPresent(System.out::println)`**: Since `max()` returns an `Optional`, we
use `ifPresent()` to print the maximum value if it's present. If the stream is
empty (i.e., no numbers greater than 100), nothing will be printed.

### **What If the Stream Is Empty?**


If no numbers in the list are greater than 100, `max()` will return an empty
`Optional`. To handle such cases safely, `Optional` can be used:

```java
max.ifPresentOrElse(
System.out::println, // Print the max value if present
() -> System.out.println("No number greater than 100") // If empty, print a
default message
);
```

### **Example 2: Using `orElse` to Provide a Default Value**


If you want to return a default value when no numbers greater than 100 are found,
you can use `orElse()`:

```java
int maxNumber = max.orElse(0); // If no max found, return 0
System.out.println(maxNumber); // Output: 234 or 0 if no number > 100
```

### **Summary:**
- First, filter the numbers greater than 100 using `filter()`.
- Then, use the `max()` method to get the maximum of the filtered numbers.
- Since `max()` returns an `Optional`, you can handle the possibility of an empty
stream safely.

In this example, the output will be `234` because it is the largest number greater
than 100 in the list.
==================================
In the context of web APIs, **Query Parameters** and **Path Parameters** are two
ways to pass data in HTTP requests. They are used in different ways and serve
different purposes.

### **1. Path Parameters:**


- **Definition:** Path parameters are part of the URL path and are used to identify
specific resources or a specific instance of a resource.
- **Usage:** Path parameters are typically used to specify a unique resource (such
as a specific user or product) in a RESTful API.
- **Syntax:** Path parameters are embedded directly in the URL path.
- **Example:**
- URL: `https://api.example.com/users/{userId}`
- Request: `https://api.example.com/users/12345`
- Here, `12345` is a path parameter (representing the unique user ID).

- **When to Use:** Use path parameters when the data being passed is essential to
locate a specific resource.
- For example, retrieving a specific user or item: `/users/{userId}` or
`/products/{productId}`.

- **Characteristics of Path Parameters:**


- Path parameters are part of the URL path and must be specified in the correct
order.
- They are typically required and can’t be omitted from the URL.
- They are used to identify a specific resource.

#### **Example Use Case for Path Parameters:**


- Fetching user details by ID:
`GET /users/{userId}`
For example: `GET /users/101` will fetch the user with ID 101.

### **2. Query Parameters:**


- **Definition:** Query parameters are key-value pairs added to the URL after the
question mark (`?`) and are used to filter, sort, or modify the request's result.
- **Usage:** Query parameters are typically used to provide optional or
supplementary information, such as search filters, sorting options, pagination
details, or other configurations.
- **Syntax:** Query parameters are appended to the URL after the `?` symbol and are
usually in the form of `key=value` pairs. Multiple query parameters are separated
by `&`.
- **Example:**
- URL: `https://api.example.com/users?age=25&status=active`
- Request: `https://api.example.com/users?age=25&status=active`
- Here, `age=25` and `status=active` are query parameters.

- **When to Use:** Query parameters are used when the data is **optional** and used
to modify or filter the response of the API.
- For example, for filtering results: `/search?query=apple&limit=10` or for
pagination: `/items?page=2&size=20`.

- **Characteristics of Query Parameters:**


- They are **optional** and can be omitted.
- Used for filtering, sorting, searching, or pagination.
- They appear after the `?` in the URL, and multiple parameters are separated by
`&`.
- They don't represent a specific resource but rather modify the behavior of the
request or the data returned.

#### **Example Use Case for Query Parameters:**


- Searching for products by category:
`GET /products?category=electronics&sort=price`
This would return products in the "electronics" category, sorted by price.

### **Key Differences Between Path Parameters and Query Parameters:**

| Aspect | Path Parameters |


Query Parameters |
|----------------------|---------------------------------------------------------|-
-------------------------------------------------------|
| **Location** | Part of the URL path. |
Appended to the URL after the `?` symbol. |
| **Purpose** | Used to identify a specific resource or resource
instance. | Used to provide optional parameters for filtering, sorting, or
modifying the result. |
| **Required/Optional** | Required (usually). |
Optional (usually). |
| **Example** | `/users/{userId}` or `/products/{productId}` |
`/users?age=25&status=active` or `/products?category=electronics&sort=price` |
| **Order** | Must follow the defined path structure. |
Can be in any order (key-value pairs). |
| **Multiple Parameters** | Not typically used for multiple values. |
Multiple query parameters can be included (separated by `&`). |
| **Use Case** | Identifying a resource (e.g., a user by ID). |
Modifying the result (e.g., filtering, pagination). |

### **Examples of Usage:**

#### **1. Path Parameter:**


```plaintext
GET /users/12345
```
- In this example, `12345` is a **path parameter**, and it refers to a specific
user with ID `12345`.

#### **2. Query Parameters:**


```plaintext
GET /users?age=25&status=active
```
- Here, `age=25` and `status=active` are **query parameters**. They don't specify a
specific user; rather, they filter the users based on their age and status.

### **Combination of Path and Query Parameters:**


You can also combine both path and query parameters in a single request.

```plaintext
GET /users/12345/orders?status=shipped&date=2024-01-01
```
- Here:
- `/users/12345` is a **path parameter** identifying a specific user.
- `status=shipped` and `date=2024-01-01` are **query parameters** that filter the
orders for that user.

### **Conclusion:**
- **Path parameters** are part of the URL and are typically used for identifying a
specific resource (such as a user or a product).
- **Query parameters** are used to provide additional optional details (such as
filtering, sorting, or pagination) and are placed after the `?` in the URL.
====================================================
### What is OpenAPI?

**OpenAPI** (formerly known as **Swagger**) is a specification for describing,


producing, consuming, and visualizing RESTful web services. It provides a
standardized way to describe APIs so that both humans and computers can understand
their capabilities and how to interact with them.

The OpenAPI Specification (OAS) defines a language-agnostic interface to REST APIs,


allowing both humans and machines to understand the functionality of an API without
having access to its source code. OpenAPI enables automatic generation of API
documentation, client SDKs, server stubs, and other tools.

### Key Features of OpenAPI:

1. **Standardized API Documentation**:


OpenAPI provides a standardized, machine-readable format (usually in YAML or
JSON) to describe the API endpoints, request/response formats, parameters,
authentication methods, and other important aspects.

2. **Tooling**:
OpenAPI is supported by a wide array of tools for tasks like:
- **Documentation Generation**: Automatically generate user-friendly API
documentation.
- **Client SDK Generation**: Generate client libraries for various programming
languages.
- **Server Stub Generation**: Generate server-side skeletons in different
programming languages.
- **Testing and Validation**: Tools like Swagger UI, Swagger Codegen, and
Postman can be used for testing APIs defined with OpenAPI.

3. **Human-Readable and Machine-Readable**:


- **Human-Readable**: OpenAPI specification allows developers to easily document
their APIs in a readable and structured format.
- **Machine-Readable**: Because OpenAPI specifications are written in a
structured format like JSON or YAML, tools can automatically parse the definition
to generate useful resources, such as client libraries, documentation, etc.

4. **Language Agnostic**:
OpenAPI is **language agnostic** and can be used with any programming language,
making it a flexible choice for a variety of development ecosystems.

5. **Swagger UI**:
OpenAPI definitions can be used to generate interactive documentation through
tools like **Swagger UI**. This allows users to test API endpoints directly from
the documentation.

### **Key Components of OpenAPI Specification:**


An OpenAPI definition contains the following key elements:

1. **Info**: Contains metadata about the API such as title, version, description,
etc.

```yaml
info:
title: Pet Store API
description: A sample API for managing a pet store
version: 1.0.0
```

2. **Paths**: Defines the available endpoints and their operations (GET, POST, PUT,
DELETE, etc.).

```yaml
paths:
/pets:
get:
summary: Get a list of pets
responses:
'200':
description: A list of pets
```

3. **Parameters**: Specifies the input parameters (query, path, header, body, etc.)
for each endpoint.

```yaml
parameters:
- in: query
name: petType
description: Type of pet to search for
required: false
schema:
type: string
```

4. **Responses**: Defines the possible responses for each operation, including


status codes and response body schema.

```yaml
responses:
'200':
description: A list of pets
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
```

5. **Schemas**: Describes the data models or objects used in requests and


responses.

```yaml
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
name:
type: string
```

6. **Security**: Specifies authentication and authorization methods (OAuth, API


keys, JWT tokens, etc.)

```yaml
security:
- api_key: []
```

7. **Servers**: Lists the base URLs where the API is hosted.

```yaml
servers:
- url: https://api.example.com/v1
```

### **Advantages of OpenAPI:**

1. **Standardization**:
OpenAPI provides a consistent way to describe APIs. This standardization ensures
that developers and tools can easily interpret and interact with APIs, regardless
of the technology stack used to build them.

2. **Tooling Ecosystem**:
The OpenAPI specification has a large ecosystem of tools for testing,
documentation, client code generation, and server stub generation, which can save
time and effort during development.

3. **Interactive Documentation**:
With OpenAPI, you can generate **interactive documentation** (e.g., using
**Swagger UI**), which allows developers to try out the API directly from the
documentation page.

4. **API Versioning and Evolution**:


OpenAPI allows you to easily version and evolve your API. You can track changes
to the API, document breaking changes, and ensure compatibility with client
applications.

5. **Code Generation**:
OpenAPI can generate client SDKs and server stubs in multiple languages, making
it easy to integrate APIs with other systems and services.

6. **Cross-team Collaboration**:
Since OpenAPI provides a standardized format, teams working on different parts
of an application (e.g., backend and frontend teams) can easily collaborate by
sharing the same API documentation.

7. **Automated Testing**:
With OpenAPI, you can automate tests based on the API specification to validate
that the actual API implementation conforms to the specification.
### **OpenAPI Example:**

Here is a simple example of an OpenAPI specification for a pet store API:

```yaml
openapi: 3.0.0
info:
title: Pet Store API
description: A sample API for managing a pet store
version: 1.0.0
paths:
/pets:
get:
summary: Get all pets
responses:
'200':
description: A list of pets
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
/pets/{petId}:
get:
summary: Get a pet by ID
parameters:
- name: petId
in: path
required: true
description: ID of the pet to fetch
schema:
type: integer
responses:
'200':
description: A pet object
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
name:
type: string
species:
type: string
```

### **How OpenAPI Works in Practice:**

1. **Defining API**: The API is first described in an OpenAPI specification file


(in YAML or JSON). This file contains details about available endpoints,
parameters, responses, etc.
2. **Documenting the API**: Using tools like **Swagger UI**, you can render the
OpenAPI specification into a user-friendly interactive documentation, where users
can see the endpoints, input parameters, and try out requests directly from the
browser.

3. **Client Code Generation**: With tools like **Swagger Codegen** or **OpenAPI


Generator**, client SDKs can be generated in multiple languages. This makes it easy
for developers to integrate with the API.

4. **Server Stub Generation**: Server-side code can also be generated using the
OpenAPI specification. This provides a skeleton server implementation, which can be
further developed and customized.

### **OpenAPI Tools:**


Some of the most popular tools in the OpenAPI ecosystem include:
- **Swagger UI**: Provides a user interface to visualize and interact with the API
defined by OpenAPI.
- **Swagger Editor**: Allows you to write and validate OpenAPI specifications.
- **Swagger Codegen/OpenAPI Generator**: Generate client libraries, server stubs,
and API documentation from an OpenAPI specification.
- **Postman**: Can import OpenAPI specifications to create and test API requests.

### **Conclusion:**
OpenAPI is a powerful specification for describing and documenting REST APIs. It
promotes **standardization**, **automation**, and **interoperability** across
different teams and technologies. By using OpenAPI, you can improve the process of
designing, consuming, and maintaining APIs, and take advantage of the large
ecosystem of tools available for code generation, testing, and documentation.
==================================================

You might also like