One Stop Solution Java, Springboot, SQL, Testing, Get
One Stop Solution Java, Springboot, SQL, Testing, Get
Let's dive deep into "1. Core Java Fundamentals". This section covers the absolute basics of Java that
every developer, regardless of experience level, must know inside out.
This section forms the foundation of all Java development. A strong grasp here demonstrates your
understanding of how Java works at a fundamental level.
These three acronyms are often confused but are distinct and crucial for understanding Java's "Write
Once, Run Anywhere" philosophy.
o What it is: The heart of Java's platform independence. It's an abstract machine that
provides a runtime environment in which Java bytecode can be executed.
o Function: It takes the .class (bytecode) files generated by the Java compiler and
converts them into machine-specific code that the underlying operating system
(Windows, macOS, Linux, etc.) can understand and execute.
o Key Responsibilities:
o Platform Dependent: While Java source code is platform-independent, the JVM itself
is platform-dependent. You need a specific JVM implementation for each operating
system (e.g., a Windows JVM, a Linux JVM).
o What it is: A software package that provides the minimum requirements for running
a Java application. It contains the JVM along with the necessary Java class libraries
(like java.lang, java.util, java.io, etc.) and supporting files.
o Function: If you only want to run Java applications (not develop them), the JRE is
sufficient. It contains everything needed to execute compiled Java code.
o Contents: JVM + Java Standard Library (classes).
o Function: It includes everything in the JRE (JVM + Java Standard Library) PLUS
development tools like:
▪ javac (Java Compiler): Translates Java source code (.java files) into bytecode
(.class files).
▪ jar: Archives Java classes and resources into a single JAR file.
o When to use: If you are a Java developer, you need the JDK.
• "Write Once, Run Anywhere" Principle: This principle is achieved because the Java compiler
(javac) converts your .java source code into platform-agnostic bytecode (.class files). This
bytecode can then be executed by any JVM, regardless of the underlying operating system.
The JVM acts as a translator, making the bytecode executable on the specific machine.
Java is a strongly typed language, meaning every variable must be declared with a data type.
o Integer types:
▪ long: 64-bit signed integer (for very large numbers, append 'L' or 'l')
o Floating-point types:
o Character type:
▪ char: 16-bit Unicode character (can hold any character, including special
symbols)
o Boolean type:
o Do not store values directly but store references (memory addresses) to objects.
o Examples: String, Array, Scanner, Integer, custom class objects (e.g., MyClass).
1.3. Variables
• Local Variables:
o Default values are assigned if not explicitly initialized (e.g., 0 for numeric, false for
boolean, null for objects).
o Declared inside a class but outside any method, constructor, or block, using the static
keyword.
o Only one copy exists for the entire class, shared by all instances.
• final Variables:
o A variable declared final can be assigned a value only once. Its value cannot be
changed after initialization.
o For reference types, final means the reference itself cannot be changed to point to
another object. The contents of the object it points to can still be modified (unless
the object itself is immutable, like String).
1.4. Operators
• Relational Operators: == (equality), != (not equal), <, >, <=, >= (return boolean)
• Operator Precedence and Associativity: Important for evaluating expressions correctly. (e.g.,
multiplication before addition).
• Conditional Statements:
o switch: Allows a variable to be tested for equality against a list of values. Good for
multiple choice scenarios. (break is crucial to prevent fall-through).
• Looping Statements:
o for loop: Executes a block of code a specific number of times. Ideal when you know
the number of iterations.
o Enhanced for loop (for-each): Iterates over arrays or collections without explicitly
managing indices. Read-only access.
o while loop: Executes a block of code repeatedly as long as a condition is true. The
condition is checked before each iteration.
o do-while loop: Executes a block of code at least once, and then repeatedly as long as
a condition is true. The condition is checked after the first iteration.
• Branching Statements:
o continue: Skips the current iteration of a loop and proceeds to the next iteration.
1.6. Strings
In Java, String is a class, not a primitive type. Strings are sequences of characters.
• String (Immutable):
o String objects are immutable, meaning once created, their content cannot be
changed.
o Any operation that appears to modify a String (e.g., concatenation) actually creates a
new String object.
o This immutability makes String objects thread-safe and suitable for use as keys in
HashMaps.
o String Pool: Java maintains a "String Pool" in the Heap to optimize memory usage.
When you create a String literal (e.g., String s = "hello";), the JVM first checks if
"hello" already exists in the pool. If it does, it returns a reference to the existing
object; otherwise, it creates a new one and puts it in the pool.
o Using new String("hello") always creates a new object in the Heap, even if "hello" is
in the String Pool.
o More efficient for string manipulation than String concatenation because it avoids
creating many intermediate String objects.
o Thread-safe: All its public methods are synchronized. This makes it suitable for multi-
threaded environments, but it comes with a performance overhead compared to
StringBuilder.
A container object that holds a fixed number of values of a single data type.
• Initialization:
Java's primitive data types are not objects. Wrapper classes provide object representations for
primitive types.
• Purpose:
• Examples:
o This simplifies code by allowing you to mix primitives and wrapper objects in many
contexts.
A robust way to handle runtime errors in a program. Prevents program termination and allows for
graceful recovery.
• catch block: Catches specific types of exceptions thrown in the try block. You can have
multiple catch blocks for different exception types (most specific first).
• finally block: Always executes, regardless of whether an exception occurred or was caught.
Used for cleanup code (e.g., closing resources like file streams, database connections).
• throws keyword: Used in a method signature to declare that a method might throw one or
more specified checked exceptions. The caller of the method must then either handle or re-
declare these exceptions.
o Checked Exceptions:
▪ Occur at runtime.
▪ They typically represent programming errors that should be fixed rather than
caught (e.g., dividing by zero, accessing an invalid array index).
• Custom Exceptions: You can create your own exception classes by extending Exception (for
checked) or RuntimeException (for unchecked).
1.10. Access Modifiers
Keywords that set the accessibility (visibility) of classes, fields, methods, and constructors.
• public: Accessible from anywhere (within the same class, same package, subclasses, and
different packages). Most permissive.
• private: Accessible only within the same class. Most restrictive. Used for encapsulation.
• protected: Accessible within the same package and by subclasses (even if in different
packages).
• Default (No keyword): Accessible only within the same package. Often called "package-
private".
• this keyword:
o Refers to the current object (the object on which the method is invoked).
o Uses:
• super keyword:
o Uses:
▪ To invoke the parent class's method if it's overridden in the child class
(super.methodName();).
▪ To access parent class's instance variables if they are hidden by a child class's
variables of the same name (super.variableName;).
A non-access modifier applicable to fields, methods, blocks, and nested classes. It makes a member
belong to the class itself rather than to any specific instance.
• static variable:
• static method:
• static block:
o Can have multiple static blocks; they execute in the order they appear.
• final variable:
o The value of a final variable can be assigned only once. It becomes a constant.
o For reference types, the reference cannot be changed to point to another object, but
the object's internal state (if mutable) can still be modified.
• final method:
• final class:
o Used to prevent inheritance and ensure security or immutability (e.g., String class is
final).
• What it is: A process that automatically frees up memory occupied by objects that are no
longer reachable (referenced) by the running program.
• JVM Component: It's a daemon thread running in the background of the JVM.
• Advantages:
• System.gc() / Runtime.getRuntime().gc(): These methods are hints to the JVM to run the
garbage collector, but there's no guarantee it will run immediately. JVM decides when to run
GC based on its algorithms and memory needs.
o Heap: Where all objects (instance variables, arrays) are allocated. It's divided into
generations (Young Generation, Old Generation).
o Stack: Where method calls, local variables, and primitive data types are stored. Each
thread has its own stack.
o PC Registers: Stores the address of the next instruction to be executed for each
thread.
o Native Method Stacks: For native methods written in other languages (C/C++).
o Mark and Sweep: Marks reachable objects, then sweeps away unreachable ones.
o Copying: Copies live objects from one memory area to another, leaving dead objects
behind.
o Mark-Compact: Marks live objects, then compacts them to one end of the heap,
reducing fragmentation.
o Generational GC: Divides the heap into generations (Young, Old) assuming most
objects die young. Different algorithms are used for different generations (e.g.,
Copying for Young, Mark-Compact for Old). Common GCs include Serial, Parallel,
CMS, G1, ZGC, Shenandoah.
• Serialization:
o This byte stream can then be stored in a file, transmitted across a network, or stored
in a database.
• Deserialization:
o The reverse process: converting a byte stream back into a Java object in memory.
• transient keyword:
• Purpose: To provide type safety at compile time and eliminate the need for casting, reducing
runtime errors (ClassCastException).
• Example without Generics: List list = new ArrayList(); list.add("hello"); list.add(123); String s
= (String) list.get(0); Integer i = (Integer) list.get(1); (Here, you have to cast and there's no
compile-time check if you add wrong types).
• Type Erasure:
o During compilation, generic type information is "erased" and replaced with raw
types (e.g., List<String> becomes List).
o This ensures backward compatibility with older Java versions that didn't support
generics.
• Wildcards (?): Used in generic code to relax the restrictions on a variable's type parameter.
o Unbounded Wildcard (<?>): Represents any type. List<?> can hold a list of any type.
o Upper Bounded Wildcard (<? extends T>): Represents T or any subclass of T. You can
read from such a list, but you cannot add elements (except null) because you don't
know the exact type. (PECS principle: Producer extends).
o Lower Bounded Wildcard (<? super T>): Represents T or any superclass of T. You can
add elements of type T or its subtypes to such a list, but you can only read objects as
Object type. (PECS principle: Consumer super).
These significantly changed how Java code is written and are almost always a topic of discussion.
• Lambda Expressions:
o Use Case: Primarily used to implement functional interfaces. They simplify code for
callbacks and functional programming constructs.
• Functional Interfaces:
• Stream API:
o What it is: A sequence of elements that supports sequential and parallel aggregate
operations.
o Declarative Style: Allows you to express what you want to do (e.g., filter, map,
collect) rather than how (iterating manually).
o Non-terminal (Intermediate) Operations: Return a new stream. They are lazy and
don't process the data until a terminal operation is called. Examples: filter(), map(),
flatMap(), sorted(), distinct(), peek().
o Source -> Zero or more Intermediate Operations -> One Terminal Operation
• Optional Class:
o What it is: A container object that may or may not contain a non-null value.
o Purpose: To provide a clear way to represent the absence of a value, helping to avoid
NullPointerException (a common runtime error).
o Replacement for: The old java.util.Date and java.util.Calendar classes (which had
issues with thread-safety, mutability, and design flaws).
o Key Classes:
This detailed breakdown of "Core Java Fundamentals" should give you a very strong foundation for
your interview. Remember to not just know definitions but also how and when to use these
concepts and be able to provide small code examples.
You got it! Let's dive deep into "2. Object-Oriented Programming (OOP)". This is arguably the most
fundamental paradigm for Java and understanding its principles is crucial for designing robust,
maintainable, and scalable applications.
OOP is a programming paradigm based on the concept of "objects," which can contain data and
code: data in the form of fields (attributes or properties), and code in the form of procedures
(methods). The primary goal of OOP is to increase the flexibility and maintainability of programs.
These are the core principles that define object-oriented programming. You should be able to explain
each thoroughly, provide real-world analogies, and demonstrate them with code.
a. Encapsulation
• Concept: The binding of data (attributes/fields) and the methods that operate on that data
into a single unit (a class), and keeping them safe from outside interference and misuse. It's
about "data hiding" or "information hiding."
o Declaring instance variables as private: This prevents direct access from outside the
class.
o Providing public getter and setter methods: These "accessor" and "mutator"
methods are the controlled ways to read and modify the private data. You can add
validation logic within setters.
• Benefits:
o Control over Data: You can enforce business rules or validation logic when data is
set.
• Example:
Java
class BankAccount {
this.accountNumber = accountNumber;
this.balance = initialBalance;
} else {
return accountNumber;
return balance;
if (amount > 0) {
this.balance += amount;
} else {
this.balance -= amount;
} else {
myAccount.deposit(500);
myAccount.withdraw(200);
b. Inheritance
• Concept: The mechanism by which one class acquires the properties (fields) and behaviors
(methods) of another class. It represents an "is-a" relationship (e.g., "A Car is-a Vehicle").
• Keywords:
o extends: Used by a class to inherit from another class.
o Single Inheritance: A class inherits from only one direct parent class. (Java supports
this for classes).
o Multilevel Inheritance: A class inherits from a class, which in turn inherits from
another class (e.g., A -> B -> C).
o Multiple Inheritance: A class inheriting from multiple direct parent classes. Java
does NOT support multiple inheritance for classes to avoid the "diamond problem"
(ambiguity if parent classes have methods with same signature).
• Benefits:
o Code Reusability: Child classes don't need to rewrite code already present in the
parent.
• Example:
Java
String brand;
this.brand = brand;
System.out.println("Vehicle sound!");
}
}
class Car extends Vehicle { // Child class (Subclass) inherits from Vehicle
String model;
this.model = model;
@Override
System.out.println("Car horn!");
boolean autonomous;
super(brand, model);
this.autonomous = autonomous;
@Override
myCar.displayBrand();
myCar.drive();
tesla.charge();
c. Polymorphism
• Concept: "Many forms." It refers to the ability of an object to take on many forms or the
ability of a variable to refer to objects of different types at different times. In Java, it primarily
means that a single interface can be used for different underlying forms (data types).
▪ Concept: Occurs when there are multiple methods in the same class with
the same name but different method signatures (different number of
parameters, different types of parameters, or different order of parameters).
▪ The compiler decides which method to call based on the arguments passed
at compile time.
▪ Example:
Java
class Calculator {
return a + b;
return a + b;
return a + b + c;
// Usage:
▪ The decision of which method to call is made at runtime based on the actual
object type, not the reference type. This is also known as "Dynamic Method
Dispatch."
▪ Method must have the same name, same parameters, and same
return type.
Java
Vehicle v1 = new Vehicle("Generic");
v2.honk(); // Output: Car horn! (Runtime decision based on actual object Car)
v3.honk(); // Output: Silent electric car hum! (Runtime decision based on ElectricCar)
• Benefits:
o Code Reusability: Write generic code that can work with objects of different types,
as long as they share a common supertype.
o Maintainability: Easier to add new types without modifying existing code, just
extend the base class and override.
d. Abstraction
• Concept: The process of hiding the implementation details and showing only the essential
features of an object to the outside world. It's about "what" an object does, not "how" it
does it.
o Abstract Classes:
▪ Can have constructors (which are called by subclass constructors via super()).
o Interfaces:
▪ Before Java 8, interfaces could only have abstract methods. From Java 8, they
can have default and static methods with implementations. From Java 9,
they can also have private methods.
o Abstract Class: Use when you want to provide a base class with some common
functionality implemented, but also enforce that subclasses implement specific
methods. Ideal when you have a strong "is-a" relationship and want to share code.
o Interface: Use when you want to define a contract that multiple unrelated classes
can adhere to. Ideal for defining capabilities or behaviors that can be shared across
different class hierarchies.
• Example (Abstraction):
Java
// Abstract Class
String name;
this.name = name;
}
}
double radius;
super(name);
this.radius = radius;
@Override
double length;
double width;
super(name);
this.length = length;
this.width = width;
@Override
}
// Interface
interface Drawable {
@Override
System.out.println("Drawing a triangle.");
circle.displayInfo();
rectangle.displayInfo();
triangle.draw();
triangle.setColor("Blue"); // Using default method from interface
2.2. Constructors
• Concept: A special type of method used to initialize objects. It's invoked automatically when
an object of a class is created using the new keyword.
• Rules:
• Types of Constructors:
o Default Constructor: If you don't define any constructor, Java automatically provides
a public, no-argument default constructor. It initializes instance variables to their
default values.
• Constructor Overloading: A class can have multiple constructors with the same name but
different parameter lists (just like method overloading).
• Constructor Chaining:
o Using super(): To call a constructor of the parent class. super() or super(args) must
be the first statement in a child class constructor.
• Example:
Java
class Dog {
String name;
int age;
// No-argument constructor
public Dog() {
// Parameterized constructor
this.name = name;
this.age = age;
System.out.println("Dog object created: " + name + ", " + age + " years old.");
dog1.bark();
dog2.bark();
dog3.bark();
}
These methods (defined in java.lang.Object) are crucial when dealing with collections, especially
HashMap, HashSet, and Hashtable.
• equals() Method:
o Purpose: Used to compare two objects for equality of content (value equality), not
just reference equality (==).
o Overriding: You should override equals() whenever you want to define what it
means for two objects of your class to be logically equal, based on their content.
o Contract (Important!): If you override equals(), you must also override hashCode() to
maintain the general contract for the Object.hashCode() method.
• hashCode() Method:
o Purpose: Returns an integer hash code value for the object. This hash code is
primarily used by hash-based collections (HashMap, HashSet, Hashtable) to
determine where to store and retrieve objects efficiently.
o Contract:
1. If two objects are equals() according to the equals() method, then calling the
hashCode() method on each of the two objects must produce the same
integer result.
2. If two objects are not equals(), their hash codes do not have to be different.
However, different hash codes for unequal objects can improve the
performance of hash tables.
o If you override equals() but not hashCode(), and you put objects of your class into a
HashSet or use them as keys in a HashMap:
▪ HashSet might store two "equal" objects at different locations because their
default hashCode() values (based on memory address) would be different,
leading to duplicate "equal" objects in a set.
▪ HashMap might not be able to retrieve a value you stored because the key
you use for retrieval (which is equals() to the original key) hashes to a
different bucket.
o HashMap first checks hashCode() to narrow down the search to a specific bucket,
then uses equals() to find the exact object within that bucket. If hashCode() values
differ for equal objects, the second equals() check will never happen.
Java
class Employee {
int id;
String name;
this.id = id;
this.name = name;
// Overriding equals()
@Override
@Override
return result;
@Override
// Demonstrate in HashSet
employeeSet.add(emp1);
o Cons: Tight coupling between parent and child classes, can lead to rigid hierarchies,
"fragile base class problem" (changes in base class can break subclasses).
o A class contains an object of another class as one of its fields (instance variables).
o Instead of inheriting, you use an instance of another class to get its functionality.
o Pros:
▪ Flexibility: You can change the component class at runtime or easily swap
implementations.
▪ Better Maintainability: Changes in one class are less likely to impact others.
o Use inheritance when there's a strong, clear "is-a" relationship and you want to
leverage polymorphism and common base class implementations.
o Use composition when a class has or uses another class's functionality, but isn't
necessarily a specialized version of it. It offers greater flexibility and less coupling.
• Example:
Java
class Engine {
public void start() {
System.out.println("Engine started.");
System.out.println("Engine stopped.");
class VehicleComposition {
public VehicleComposition() {
System.out.println("Vehicle started.");
engine.stop();
System.out.println("Vehicle stopped.");
myVehicle.startVehicle();
myVehicle.stopVehicle();
}
While a full deep dive into all design patterns isn't expected, knowing a few common ones and their
purpose demonstrates good design principles.
a. Singleton Pattern
• Purpose: Ensures that a class has only one instance and provides a global point of access to
that instance.
• Use Cases: Logging, configuration management, thread pools, database connection pools,
caching.
Java
class Logger {
if (instance == null) {
return instance;
Java
class Configuration {
private static Configuration instance = new Configuration(); // Instantiated at class load time
private Configuration() {}
public static Configuration getInstance() {
return instance;
System.out.println("Configuration loaded.");
Java
class ConnectionPool {
private ConnectionPool() {}
return instance;
4. Static Inner Class (Bill Pugh Singleton - most common and recommended): This is
the most widely accepted approach as it combines lazy loading with thread safety
without explicit synchronization.
Java
class MySingleton {
private MySingleton() {}
// Static inner class. Not loaded until getInstance() is called.
return SingletonHelper.INSTANCE;
Java
// Usage: EnumSingleton.INSTANCE.doSomething();
• Purpose: Provides an interface for creating objects in a superclass, but allows subclasses to
alter the type of objects that will be created. It's about "deferring instantiation to
subclasses."
• Use Cases: When a class cannot anticipate the type of objects it needs to create, or when a
class wants its subclasses to specify the objects to be created.1
• Example:2
Java
// Product Interface
interface Notification {
void notifyUser();
// Concrete Products
@Override
@Override
// Concrete Creators
@Override
}
class SMSNotificationFactory extends NotificationFactory {
@Override
email.notifyUser();
sms.notifyUser();
c. Builder Pattern
• Use Cases: When an object has many optional parameters, or when its construction involves
multiple steps or requires various configurations. Avoids "telescoping constructors."
• Example:
Java
class Pizza {
this.dough = builder.dough;
this.sauce = builder.sauce;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.mushrooms = builder.mushrooms;
@Override
return "Pizza [dough=" + dough + ", sauce=" + sauce + ", cheese=" + cheese +
this.dough = dough;
this.sauce = sauce;
this.cheese = cheese;
}
public PizzaBuilder addPepperoni(boolean pepperoni) {
this.pepperoni = pepperoni;
this.mushrooms = mushrooms;
return this;
.addMushrooms(true)
.build();
System.out.println(veggiePizza);
.addPepperoni(true)
.build();
System.out.println(meatLovers);
d. Observer Pattern
• Purpose: Defines a one-to-many dependency between objects so that when one object
changes state, all its dependents are notified and updated automatically.
• Use Cases: Event handling systems, real-time data updates (e.g., stock tickers), UI
components reacting to model changes.
• Components:
o Subject (Observable): The object that maintains a list of its dependents (observers)
and notifies them of state changes.
• Example (Conceptual, using Java's built-in Observer/Observable is deprecated but good for
understanding; often custom interfaces are used):
Java
import java.util.ArrayList;
import java.util.List;
// Subject interface
interface Subject {
// Concrete Subject
this.news = news;
@Override
public void addObserver(Observer o) {
observers.add(o);
@Override
observers.remove(o);
@Override
observer.update(message);
// Observer interface
interface Observer {
// Concrete Observers
this.channelName = channelName;
@Override
public void update(String news) {
agency.addObserver(channel1);
agency.addObserver(channel2);
// Output:
agency.removeObserver(channel1);
agency.addObserver(channel3);
// Output:
}
Mastering these OOP concepts is fundamental. Be ready to explain them theoretically, draw
diagrams if needed, and write simple code demonstrating each principle. Good luck!
Alright, let's deep dive into "3. Collections Framework". This is an absolutely critical topic for any
Java developer, especially one with 3 years of experience. You'll use these classes daily, and
interviewers often ask about their internal workings, performance characteristics, and thread-safety
aspects.
The Java Collections Framework (JCF) is a set of interfaces and classes that represent groups of
objects as a single unit. It provides a unified architecture for storing and manipulating collections of
objects.
3.1. Hierarchy
Understanding the hierarchy is key to understanding the relationships and shared behaviors among
different collection types.
o Extends Iterable<E>.
o Defines common behaviors for all collections: add(), remove(), contains(), size(),
isEmpty(), toArray(), clear(), etc.
• List<E> (Interface):
o Extends Collection<E>.
o Provides methods like get(int index), set(int index, E element), add(int index, E
element), remove(int index), indexOf(), lastIndexOf(), subList().
• Set<E> (Interface):
o Extends Collection<E>.
o Does not guarantee order (except for LinkedHashSet which maintains insertion
order, and TreeSet which maintains sorted order).
o The add() method returns true if the element was added (i.e., it was not a duplicate)
and false otherwise.
• Queue<E> (Interface):
o Extends Collection<E>.
o Typically orders elements in a FIFO (First-In, First-Out) manner, but other orderings
are possible (e.g., PriorityQueue).
o Provides specific methods for adding (offer), removing (poll, remove), and inspecting
(peek, element) elements from the "head" of the queue.
o Provides methods like put(K key, V value), get(Object key), remove(Object key),
containsKey(), containsValue(), keySet(), values(), entrySet().
• ArrayList:
o Performance:
▪ get(index): O(1) - constant time, very fast due to direct index access.
▪ add(element) (at end): Amortized O(1) - usually fast, but can be O(N) if
resizing is needed (doubles its capacity).
o Use Case: When you need frequent random access to elements and fewer
insertions/deletions in the middle.
• LinkedList:
o Performance:
▪ add(index, element) (in middle): O(N) to find the index, then O(1) for
insertion. Better than ArrayList if the current node is already known.
▪ Iteration: Slower than ArrayList for direct index access, but fast using an
Iterator.
o Use Case: When you need frequent insertions and deletions at the ends or in the
middle, and less frequent random access. Also implements Queue and Deque
interfaces, making it suitable for queues, stacks, and double-ended queues.
• Vector (Legacy):
o Use Case: Rarely used in modern Java code unless thread-safety is strictly required
for all operations on a single list instance, and Collections.synchronizedList() or
CopyOnWriteArrayList are not viable. Generally, ArrayList with explicit
synchronization or concurrent collections are preferred.
• Stack (Legacy):
o Methods: push() (add), pop() (remove and return top), peek() (return top without
removing), empty(), search().
o Use Case: Should be avoided. For LIFO stack functionality, use ArrayDeque which is
more efficient and provides better performance, or LinkedList as a Deque.
• HashSet:
o Internal Data Structure: Uses a HashMap internally to store its elements (elements
are stored as keys in the underlying HashMap, with a dummy value).
o Performance:
o Important: Relies on hashCode() and equals() methods of the stored objects for
uniqueness. If you override equals(), you must override hashCode().
o Use Case: When you need to store unique elements and the order doesn't matter,
and you need fast lookups.
• LinkedHashSet:
o Order: Maintains insertion order (the order in which elements were added).
o Performance: Slightly slower than HashSet for insertions and deletions due to
maintaining the linked list, but still O(1) on average. Iteration is faster than HashSet
because it follows the insertion order link.
o Use Case: When you need unique elements and also want to preserve the order of
insertion.
• TreeSet:
o Nulls: Does NOT allow null elements (throws NullPointerException on add if the set
is not empty or on first element if Comparator is used that cannot handle null).
o Performance:
o Use Case: When you need to store unique elements in a sorted order.
• HashMap:
o Internal Data Structure: Array of linked lists (or Red-Black trees in Java 8+ for large
buckets).
o Performance:
▪ put(), get(), remove(): O(1) on average (constant time), assuming good hash
function for keys. In worst case (many collisions), can degrade to O(N).
o Important: Relies on hashCode() and equals() methods of the keys for uniqueness
and retrieval.
o Use Case: Most commonly used Map implementation when order is not important
and you need fast key-based lookups.
• LinkedHashMap:
o Internal Data Structure: HashMap + doubly-linked list running through its entries.
o Order: Maintains insertion order by default. Can also be configured for access order
(elements move to the end of the list when accessed, useful for LRU caches).
o Keys/Values: Allows one null key and multiple null values.
o Performance: Slightly slower than HashMap for additions and deletions due to
maintaining the linked list, but still O(1) on average. Iteration is faster than HashMap
because it follows the insertion/access order.
o Use Case: When you need a map that preserves the order of key-value pair insertion
(or access).
• TreeMap:
o Order: Stores entries in sorted order based on the keys (natural order or custom
Comparator).
o Performance:
o Use Case: When you need a map where keys are stored in a sorted order, or when
you need to perform range queries (e.g., subMap()).
• Hashtable (Legacy):
o Keys/Values: Does NOT allow null keys or null values. Throws NullPointerException.
o Race Conditions: Multiple threads trying to modify the collection simultaneously can
lead to inconsistent state.
o ConcurrentModificationException: If one thread iterates over a collection while
another thread modifies it (add/remove), the iterator can become invalid, leading to
this runtime exception.
1. Collections.synchronizedList(), Collections.synchronizedSet(),
Collections.synchronizedMap():
▪ How it works: These are static factory methods that return a synchronized
(wrapper) view of a non-synchronized collection. They internally wrap the
methods of the underlying collection with synchronized blocks.
▪ Drawbacks:
▪ Performance: Can be slow due to single lock for the entire collection
(high contention).
Java
synchronized (synchronizedList) {
// ...
▪ ConcurrentHashMap:
▪ Concept: Queues that support operations that wait for the queue to
become non-empty when retrieving an element, and wait for space
to become available when storing an element.
o Fail-Fast Iterators:
o Fail-Safe Iterators:
▪ They operate on a clone or snapshot of the collection at the time the iterator
was created.
o Purpose: Defines the natural or default sorting order for objects of a class.
o Implementation: The class whose objects you want to sort must implement this
interface.
o compareTo() method:
▪ obj1.compareTo(obj2):
o Purpose: Defines an alternative or custom sorting order for objects. Useful when:
▪ You need multiple ways to sort the same objects (e.g., sort Employee by
name, id, salary).
o compare() method:
• Example:
Java
import java.util.*;
String name;
int age;
this.name = name;
this.age = age;
@Override
@Override
people.forEach(System.out::println);
Collections.sort(people, nameComparator);
people.forEach(System.out::println);
sortedPeopleByAge.forEach(System.out::println);
The Collections Framework is vast, but focusing on these core interfaces and their most common
implementations, their performance characteristics, and thread-safety concerns will make you well-
prepared for interview questions. Be ready to discuss specific scenarios for when to use ArrayList vs.
LinkedList, HashMap vs. TreeMap, and HashMap vs. ConcurrentHashMap.
Got it. Let's delve into "4. Multithreading and Concurrency" in detail. This is a crucial topic for a 3-
year experienced Java developer, as it directly relates to building high-performance, responsive, and
scalable applications. Interviewers will often ask about thread creation, synchronization, common
concurrency problems, and the java.util.concurrent package.
Multithreading allows a program to execute multiple parts of its code concurrently within a single
process. Each independent part of execution is called a thread.
• What is a Thread?
o Threads share the same memory space (heap) of the process, but each has its own
call stack and PC (Program Counter) register.3
• Advantages of Multithreading:
o Better Resource Sharing: Threads within the same process can easily share data.7
• Disadvantages of Multithreading:
o Complexity: Harder to design, debug, and test concurrent applications due to race
conditions, deadlocks, etc.
o Overhead: Context switching between threads incurs some overhead.
o Resource Contention: Multiple threads accessing shared resources can lead to data
corruption or inconsistent states.8
• Creating Threads: In Java, there are two primary ways to create and run threads:9
▪ Override the run() method with the code that the thread will execute.
▪ Create an instance of your class and call its start() method (which in turn
calls run() in a new thread).
▪ Limitation: Java does not support multiple inheritance, so your class cannot
extend any other class if you choose this approach.
Java
@Override
Java
@Override
thread2.start();
Java
import java.util.concurrent.*;
this.number = number;
@Override
• Thread Lifecycle: A thread goes through various states from its creation to its termination.
o NEW: A thread that has been created but not yet started (i.e., start() method not
called).
o RUNNABLE: A thread that is executing or ready to execute. It's waiting for its turn on
the CPU. It includes both "Running" and "Ready" states.
o BLOCKED: A thread that is waiting for a monitor lock (e.g., waiting to enter a
synchronized block/method) to access a shared resource.14
o TERMINATED: A thread that has finished its execution (its run() method has
completed or thrown an uncaught exception).
4.2. Synchronization
The process of controlling access to shared resources by multiple threads to prevent race conditions
and ensure data consistency.15
• Race Condition: Occurs when multiple threads try to access and modify a shared resource
simultaneously, and the final outcome depends on the unpredictable order of execution of
these threads.16
1. synchronized Keyword:
▪ synchronized method:
▪ Java
▪ class Counter {
▪ count++;
▪ }
▪ return count;
▪ }
▪ }
▪ synchronized block:
▪ Java
▪ class SharedResource {
▪ data++;
▪ }
▪ }
▪ this.data = newValue;
▪ }
▪ }
▪ }
▪ Intrinsic Lock (Monitor Lock): Every Java object has an intrinsic lock
associated with it.
2. volatile Keyword:
▪ Use Case: When one thread writes to a variable and other threads only read
that variable, and you need to ensure readers see the latest value. (e.g., a
flag to stop a thread).
▪ Java
▪ class VolatileExample {
▪ while (running) {
▪ // Do work
▪ }
▪ System.out.println("Thread stopped.");
▪ }).start();
▪ }
▪ }
▪ }
▪ From Object class: These methods are used for inter-thread communication
and must be called from within a synchronized block/method, on the object
whose intrinsic lock is held.
▪ wait(): Causes the current thread to release the lock on the object and go
into a WAITING or TIMED_WAITING state until another thread invokes
notify() or notifyAll() on the same object, or the specified timeout expires.
▪ notifyAll(): Wakes up all threads that are waiting on this object's monitor.
class Message {
while (isEmpty) {
try {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
isEmpty = true;
return msg;
while (!isEmpty) {
try {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
this.msg = msg;
isEmpty = false;
}
4.3. Thread Pools (ExecutorService)
• Problem: Creating a new thread for every task is expensive in terms of resource consumption
(memory, CPU cycles for context switching).
• Solution: Thread pools manage a pool of worker threads. Tasks are submitted to the pool,
and the pool's threads execute them.
• Executors (Utility Class): Provides static factory methods to create common ExecutorService
configurations.19
• Lifecycle: ExecutorService needs to be explicitly shut down using shutdown() (waits for
current tasks to complete) or shutdownNow() (attempts to stop all running tasks
immediately).24
• Benefits:
This package introduced powerful, high-level concurrency constructs that are generally preferred
over low-level wait()/notify() or synchronized for complex scenarios, due to their ease of use,
robustness, and performance.26
o Problem with synchronized: Limited flexibility (must release lock in the same block,
cannot interrupt a waiting thread, cannot try to acquire a lock without blocking,
cannot acquire multiple locks in specific order).
o ReentrantLock: An explicit lock implementation that provides more flexibility than
synchronized.
• CountDownLatch:
o Purpose: A synchronization aid that allows one or more threads to wait until a set of
operations being performed in other threads completes.
o Mechanism: Initialized with a count. Threads call countDown() when their task is
complete.33 A thread waiting on the latch calls await() and blocks until the count
reaches zero.34
o Use Case: Start multiple threads, do some concurrent work, and then wait for all of
them to finish before proceeding.
• CyclicBarrier:
o Purpose: A synchronization aid that allows a set of threads to wait for each other to
reach a common barrier point.35
o Mechanism: Initialized with a number of parties. Threads call await() when they
reach the barrier. All threads block until all parties have called await().
o Reusability: The barrier can be reused once all threads have passed it.36
o Use Case: Useful for scenarios where a group of threads needs to perform a series of
operations in stages, with all threads needing to complete a stage before moving to
the next.
• Semaphore:
o Blocking Operations:
o Purpose: Provide atomic operations on single variables without using explicit locks.
o Benefits: More performant and scalable than using synchronized for simple variable
updates.
o Use Case: Atomic counters, flags, or references that need to be updated safely by
multiple threads.
4.5. Deadlock
• What it is: A situation where two or more threads are blocked indefinitely, each waiting for
the other to release a resource that it needs.39
1. Mutual Exclusion: At least one resource must be held in a non-sharable mode (e.g.,
a lock).
2. Hold and Wait: A thread holding at least one resource is waiting to acquire
additional resources held by other threads.40
4. Circular Wait: A set of threads T1, T2, ..., Tn exists such that T1 is waiting for a
resource held by T2, T2 is waiting for a resource held by T3, and so on, with Tn
waiting for a resource held by T1.
synchronized (lock1) {
}, "Thread-1").start();
synchronized (lock2) {
}, "Thread-2").start();
}
• How to Prevent Deadlock:
o Break Circular Wait: The most common strategy. Establish a consistent ordering of
resource acquisition. If Thread 1 acquires Lock A then Lock B, Thread 2 should also
acquire Lock A then Lock B, never Lock B then Lock A.
o Avoid Hold and Wait: Acquire all necessary resources at once, or release all held
resources if additional ones cannot be acquired.
o Allow Preemption: If a thread cannot acquire a resource, it releases the ones it holds
and retries.
o Avoid Mutual Exclusion (if possible): Use concurrent data structures that don't
require explicit locking (e.g., ConcurrentHashMap).
o Thread Dumps: Analyze thread dumps (jstack <pid>). They will show threads in
BLOCKED state, often with waiting for monitor entry on specific locks and holding
lock on others.
• How to Resolve Deadlock: Usually involves fixing the code to prevent one of the four
conditions from being met, most commonly by ensuring a consistent lock acquisition order.
Multithreading and Concurrency are vast and complex topics. For a 3-year experienced developer,
the expectation is to not just know the basics, but also to understand the trade-offs of different
synchronization mechanisms, when to use the java.util.concurrent utilities, and how to identify and
prevent common concurrency issues like deadlocks. Practice writing concurrent code and debugging
it to solidify your understanding.
Got it! Let's dive deep into "5. Spring Boot & Microservices". This is a crucial area for a Java
developer with 3 years of experience, as Spring Boot is the de-facto standard for building modern
Java applications, especially in the context of microservices.
Spring Boot is an opinionated framework that simplifies the development of production-ready, stand-
alone Spring applications.1 It builds on top of the Spring Framework, making it easier to get Spring
applications up and running with minimal configuration.2 Microservices are an architectural style that
structures an application as a collection of loosely coupled, independently deployable services.3
Spring Boot is built on the foundation of the Spring Framework.4 Understanding these core concepts
is vital.
o How it works: Spring scans your application, identifies components, creates their
instances, and manages their lifecycle (creation, configuration, destruction).5
o Benefits: Reduces boilerplate code, promotes loose coupling, easier testing, and
promotes modularity.6
▪ Example:
Java
@Service
this.userRepository = userRepository;
▪ Example:
Java
@Service
@Autowired
this.productRepository = productRepository;
▪ Cons:
▪ Example:
Java
@Service
@Autowired
b. Bean Lifecycle
• Bean: An object that is instantiated, assembled, and managed by a Spring IoC container. They
are the backbone of your application and form the objects that are managed by the Spring
IoC container.12
3. Initialization:
4. Ready for Use: The bean is ready for use by the application.
5. Destruction:
1. singleton (Default):
▪ Concept: Only one single instance of the bean is created per Spring IoC
container. This is the most common scope.
2. prototype:
▪ Use Case: Stateful beans that need a fresh instance for each client.
c. Annotations
Spring Boot heavily relies on annotations for configuration and defining components.16
• @Component: A generic stereotype annotation indicating that an annotated class is a
"component" and should be managed by the Spring IoC container.17 It's a general-purpose
annotation for any Spring-managed component.
o Can be used on constructors, setter methods, fields, and even method parameters.25
• @Qualifier("beanName"): Used with @Autowired when there are multiple beans of the
same type.26 It helps specify exactly which bean to inject by its name.
• @Bean:
o Indicates that a method produces a bean to be managed by the Spring IoC container.
The method's return value will be registered as a Spring bean.
• @Configuration:
o Spring processes these classes to generate bean definitions and service requests for
those beans at runtime.
o Often used for externalizing bean definitions or for complex configurations that
cannot be done with simple stereotype annotations.
o Aims to get you up and running with minimal fuss by providing sensible defaults.30
o No XML Configuration: Largely eliminates the need for verbose XML configurations;
prefers annotation-based and JavaConfig.
o Uses @RestController to define controllers that handle incoming HTTP requests and
return JSON/XML responses.34
o @RequestBody: Maps the HTTP request body to a domain object (e.g., converting
JSON request body to a Java object).41 Requires a JSON/XML processing library like
Jackson.
o ResponseEntity<T>: Allows you to control the entire HTTP response (status code,
headers, body).43
o @ControllerAdvice / @RestControllerAdvice:
• application.properties/application.yml:
o Spring Boot automatically loads these files. Profiles can be used (application-
dev.properties, application-prod.yml) for environment-specific configurations.47
Spring Data JPA simplifies data access layer development by providing abstractions over JPA (Java
Persistence API) and Hibernate (a popular JPA implementation).48
o @Entity: Marks a plain old Java object (POJO) as a JPA entity, meaning it maps to a
table in the database.50
• JpaRepository / CrudRepository:
o Spring Data JPA provides these interfaces that offer out-of-the-box CRUD (Create,
Read, Update, Delete) operations and query methods without writing any
implementation code.54
• Derived Query Methods: Spring Data JPA can automatically generate queries from method
names in your repository interfaces.56
o findByAgeGreaterThan(int age)
o findByEmailContainingIgnoreCase(String email)
▪ Associated data is not loaded from the database immediately when the main
entity is loaded.
▪ It's loaded only when it's explicitly accessed (e.g., when you call a getter
method on the collection).
▪ Cons: Can lead to N+1 query problem if not managed properly (fetching child
entities one by one in a loop).
▪ Associated data is loaded from the database immediately along with the
main entity.
o Recommendation: Prefer lazy loading and use explicit fetching strategies (e.g., JOIN
FETCH in JPQL queries or @EntityGraph) when you know you need the associated
data to avoid N+1 issues.
While "microservices" is an architectural style, Spring Boot is a primary tool for building them.57
o Each service:
▪ Is independently deployable.
o Resilience: Failure in one service doesn't necessarily bring down the entire
application.
• Disadvantages:
o API Gateway:
▪ Concept: A single entry point for all client requests. It acts as a reverse proxy
that routes requests to the appropriate microservice.64
o Service Discovery:
▪ Types:
o Circuit Breaker:
o Centralized Configuration:
▪ Mechanism: One service makes a direct HTTP request (GET, POST, etc.) to
another service and waits for a response.
Spring Boot and Microservices are vast topics, but for a 3-year experienced role, you're expected to
have hands-on experience building REST APIs with Spring Boot, understanding how Spring manages
dependencies, interacting with databases using Spring Data JPA, and a good conceptual grasp of
microservices architecture and common patterns. Be prepared to discuss your project experiences
with these technologies!
Alright, let's break down "6. Database Technologies & ORM" in detail. This topic is fundamental for
any backend developer, and for someone with 3 years of experience, you're expected to have a solid
understanding of both relational databases and how Java applications interact with them, specifically
through JPA/Hibernate, which we briefly touched upon in the previous topic.
This section covers the core concepts of databases, particularly relational databases, SQL, and the
crucial role of Object-Relational Mapping (ORM) tools like JPA and Hibernate in Java applications.
o Concept: A type of database that stores and provides access to data points that are
related to one another. Data is organized into tables (relations), which consist of
rows (records) and columns (attributes).
o Key Characteristics:
o Transaction: A single logical unit of work that either completes entirely or fails
entirely.
o Atomicity:
▪ Analogy: A money transfer from account A to B. It's either debit A and credit
B, or neither.
o Consistency:
▪ Concept: A transaction brings the database from one valid state to another
valid state. It ensures that all data integrity rules (e.g., foreign key
constraints, unique constraints, check constraints) are maintained before
and after the transaction.
▪ Analogy: After a money transfer, the total sum of money across accounts
remains the same.
o Isolation:
▪ Concept: Concurrent transactions do not interfere with each other. Each
transaction executes as if it were the only transaction running in the system.
The intermediate state of a transaction is not visible to other concurrent
transactions.
o Durability:
▪ TRUNCATE: Remove all rows from a table (DDL, faster than DELETE, cannot
be rolled back).
▪ SAVEPOINT: Set a point within a transaction to which you can roll back.
o WHERE clause
o GROUP BY clause with aggregate functions (COUNT, SUM, AVG, MIN, MAX)
o ORDER BY clause
• Indexes:
o Concept: A special lookup table that the database search engine can use to speed up
data retrieval operations. Like an index in a book.
o Types:
▪ Clustered Index: Determines the physical order of data storage in the table.
A table can have only one clustered index (often the Primary Key).
o Drawbacks:
▪ Increases storage space.
▪ Slows down INSERT, UPDATE, and DELETE operations because the index itself
must also be updated.
• Concept: A Java API that provides a standard way for Java applications to connect to and
interact with various relational databases.
• Architecture:
o JDBC API: The interfaces and classes defined in java.sql and javax.sql.
6. Close resources (in finally blocks): ResultSet, Statement, Connection (in that order).
• Problem with JDBC: Verbose, requires lots of boilerplate code for common operations, error-
prone (forgetting to close resources). This led to the development of ORMs.
• Key Features:
o Annotations: Uses annotations (e.g., @Entity, @Table, @Id) to map Java classes to
database tables and fields to columns.
o EntityManager: The primary interface for interacting with the persistence context (a
set of managed entities). Used for performing CRUD operations, finding entities, and
querying.
• Why JPA? It hides the complexity of JDBC, allowing developers to focus on business logic
rather than SQL.
6.4. Hibernate
• Features:
o Native SQL, HQL (Hibernate Query Language), Criteria API: Provides various ways to
query data. HQL is similar to JPQL but has some Hibernate-specific extensions.
o Dirty Checking:
• Scenario:
o Imagine Book and Author entities, where a Book has one Author (@ManyToOne) and
an Author can have multiple Books (@OneToMany).
o If you load N Author entities and then iterate through them to access their books
collection (which is lazy-loaded), Hibernate will execute N separate queries to fetch
the books for each author.
• Example (Conceptual):
Java
System.out.println(author.getName());
for (Book book : author.getBooks()) { // N queries, one for each author's books
2. JOIN FETCH in JPQL/HQL: (Most common and recommended for specific use cases)
▪ Explicitly tells the ORM to fetch the associated collection/entity in the same
query using a JOIN clause.
▪ Java
▪ List<Author> findAllAuthorsWithBooks();
▪ Provides a way to define a "fetch plan" for entities and their associated
relationships.
▪ You can specify which attributes of an entity graph (e.g., entity itself and its
associations) to fetch eagerly.
▪ Java
▪ @EntityGraph(attributePaths = {"books"})
▪ List<Author> findAll();
▪ Benefits: Cleaner than JOIN FETCH for simple graph fetching, often
preferred.
▪ Type: Can be FETCH (forces joins) or LOAD (allows lazy loading for unspecifed
attributes).
▪ Sometimes, it's better to fetch entities and their related data in separate
queries if the relations are complex or the data is not always needed
together.
▪ Instead of fetching entire entities, fetch only the necessary data into a DTO.
This avoids loading associated lazy collections at all if you only need a subset
of data.
• Database Normalization:
o Normal Forms (1NF, 2NF, 3NF, BCNF): A set of guidelines for database design.
▪ 1NF (First Normal Form): Each column must contain atomic (indivisible)
values. No repeating groups.
▪ 2NF (Second Normal Form): Must be in 1NF, and all non-key attributes must
be fully dependent on the primary key. (Eliminates partial dependencies).
▪ 3NF (Third Normal Form): Must be in 2NF, and all non-key attributes must
not depend on other non-key attributes (eliminate transitive dependencies).
o Benefits:
o Drawbacks:
▪ Can lead to more tables and more complex JOIN operations, potentially
impacting read performance.
• Database Denormalization:
o Concept: The process of intentionally introducing redundancy into a database
schema, often by combining data from multiple tables into a single table. It's
typically done after a database has been normalized.
o Trade-offs:
o When to Use: When read performance is critical and outweighs the risks of data
redundancy and potential inconsistency, especially in data warehousing, reporting,
or heavily read-optimized applications. Careful management (e.g., using triggers or
batch processes to maintain consistency) is often required.
A strong understanding of these database concepts, particularly SQL, ACID properties, transaction
isolation levels, and ORM usage (JPA/Hibernate with Spring Data JPA), including performance
considerations like the N+1 problem, is essential for a seasoned Java developer. Be ready to discuss
specific examples from your projects.
Alright, let's break down "7. Testing" in detail. For a Java developer with 3 years of experience,
testing is not just a concept but a critical part of the daily development workflow. You're expected to
write effective tests, understand different testing levels, and use modern testing frameworks and
practices.
Testing is an integral part of the software development lifecycle, aimed at verifying that an
application functions as expected, meets requirements, and is free of defects. In modern Java
development, a robust testing strategy is crucial.
• Concept: The smallest level of testing, where individual units or components of a software
are tested in isolation from the rest of the application.
• Purpose: To verify that each unit of the source code performs exactly as intended.
• Characteristics:
o Isolated: Units are tested independently, often using mocks or stubs to isolate them
from their dependencies (e.g., databases, external services, other complex classes).
o Fast: Unit tests should execute very quickly, allowing developers to run them
frequently.
o High Coverage: Aims for high code coverage to ensure most of the logic is tested.
• Frameworks/Tools:
o JUnit 5 (Jupiter, Platform, Vintage): The de-facto standard testing framework for
Java.
▪ Purpose: To create mock objects for dependencies that are too complex,
slow, or external to be used in a unit test.
Java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
class UserService {
this.userRepository = userRepository;
if (user != null) {
return null;
userRepository.save(newUser);
// A dependency interface
interface UserRepository {
User findById(Long id);
// A simple POJO
class User {
this.firstName = firstName;
this.lastName = lastName;
class UserServiceTest {
@Test
void testGetUserFullName_ExistingUser() {
// Define mock behavior: when findById(1L) is called, return a specific User object
// Assertions
verify(userRepository, times(1)).findById(1L);
@Test
void testGetUserFullName_NonExistingUser() {
when(userRepository.findById(2L)).thenReturn(null);
assertEquals(null, fullName);
verify(userRepository, times(1)).findById(2L);
@Test
void testCreateUser() {
userService.createUser("Jane", "Smith");
// Verify that save was called exactly once with any User object
verify(userRepository, times(1)).save(Mockito.any(User.class));
• Characteristics:
o Slower: Can take longer to run due to external dependencies and setup.
o Automated: Still automated, often run less frequently than unit tests.
• Frameworks/Tools:
o Spring Boot Test: Provides excellent support for writing integration tests for Spring
Boot applications.
▪ @SpringBootTest: Loads the full Spring application context (or a slice of it).
Can be configured to run with a specific WebEnvironment (e.g.,
RANDOM_PORT).
▪ @Autowired: Can inject actual Spring beans from the context into the test.
o MockMvc: Provided by Spring Test. Used for testing Spring MVC controllers without
starting a full HTTP server. It simulates HTTP requests and responses.
o WireMock: A mock server for HTTP-based APIs. Useful for simulating external service
dependencies in integration tests.
Java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
@RestController
class MyController {
this.myService = myService;
@GetMapping("/hello")
return myService.greet();
String greet();
class MyControllerIntegrationTest {
@Autowired
@Test
• Concept: A software development process where tests are written before the actual code
that implements the functionality. It's an iterative development approach.
1. Red (Write a failing test): Write a new unit test for a small piece of functionality that
doesn't exist yet. The test should fail.
2. Green (Make the test pass): Write just enough production code to make the failing
test pass. Don't worry about perfect design or optimization yet.
3. Refactor (Improve the code): Once the test passes, refactor the production code
(and potentially the test code) to improve its design, readability, and performance,
ensuring all tests still pass.
• Benefits:
o Forces Good Design: Encourages writing small, focused, testable units of code.
o Improved Code Quality: Leads to cleaner, more modular code with fewer bugs.
o Built-in Regression Suite: Every new feature adds to your safety net of tests.
o Requires discipline.
• Mocking (@Mock):
o Behavior: By default, mocks do nothing. You must explicitly define the behavior of
any method calls you expect on the mock using when().thenReturn() (stubbing).
o Use Case: When you want to isolate the code under test completely from its
dependencies, especially when dependencies are complex, external, or have side
effects. You're testing how the code under test interacts with its dependencies.
o Example: UserRepository in the Unit Testing example above. We don't want to hit a
real database; we just want to control what findById returns.
o Behavior: By default, a spy invokes the real methods of the actual object. You can
selectively override (stub) certain method calls if needed, while other methods will
still call the original implementation.
o Use Case: When you want to test a real object but need to mock only specific
methods of that object, or when you want to verify calls on a real object. This is
useful for legacy code or when the object itself has internal state you want to
preserve for some methods, while mocking others.
o Example: If you have a complex PaymentProcessor that performs many steps, and
you only want to mock the final sendToGateway() call while letting other calculation
methods run normally.
o Spy: A real object that you observe/partially override. It does its real work unless you
explicitly tell it otherwise.
• Example (Spying):
Java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
class Calculator {
return a + b;
return a * b;
@ExtendWith(MockitoExtension.class)
class CalculatorTest {
private Calculator calculator; // Mockito will create a real instance and wrap it
@Test
void testAddWithSpy() {
assertEquals(5, result);
}
@Test
void testMultiplyWithSpyAndStubbing() {
when(calculator.multiply(2, 3)).thenReturn(100);
assertEquals(100, result);
• Concept: A metric that measures the percentage of your application's source code that is
executed by your test suite. It helps identify untested areas of your code.
• Types of Coverage:
o Branch Coverage / Decision Coverage: Percentage of if/else, switch, loops where all
branches (true/false paths) were executed.
• Tools:
o JaCoCo (Java Code Coverage): A popular open-source code coverage library for Java.
Integrates well with Maven/Gradle. Generates reports (HTML, XML, CSV) showing
coverage percentages and highlighting uncovered lines.
• Importance:
• Limitations: High code coverage does not guarantee a bug-free application. It only tells you
what code was run, not if the code works correctly for all possible scenarios or edge cases.
It's a quantitative metric, not qualitative. You can have 100% coverage but still have logical
bugs if your assertions are weak or you miss edge cases in your tests.
For a 3-year experienced Java developer, you should be comfortable not only writing unit and
integration tests but also explaining their purpose, the trade-offs, and the tools involved. You should
be familiar with TDD principles and why it's beneficial. Understanding the nuances of mocking vs.
spying and the meaning of code coverage are also key indicators of a solid testing background.
Got it! Let's delve into "8. Version Control (Git)" in detail. For a Java developer (or any developer for
that matter) with 3 years of experience, a deep understanding of Git is non-negotiable. It's the
backbone of collaborative development and project management.
Version Control Systems (VCS) are tools that help software teams manage changes to source code
over time. They keep track of every modification made to the code, allowing developers to revert to
previous versions, compare changes, and collaborate efficiently without overwriting each other's
work.
Git is by far the most popular distributed version control system (DVCS).
• Branching & Merging: Facilitates parallel development of features and bug fixes without
affecting the main codebase.
• Backup & Disaster Recovery: The entire history is distributed, providing redundancy.
o Unlike centralized VCS (like SVN, Perforce) where there's a single central repository,
in a DVCS, every developer has a complete copy of the entire repository (including
its full history) on their local machine.
o Benefits:
▪ Offline Work: Developers can commit changes locally even without network
access.
• Core Concepts:
o Repository (Repo): A .git directory at the root of your project that contains all the
history, commits, and other metadata for your project.
o Commit: A snapshot of your project's files at a specific point in time. Each commit
has a unique SHA-1 hash, a commit message, author, date, and a pointer to its
parent commit(s).
o Working Directory: The actual files you are currently working on in your file system.
3. git status: Shows the status of your working directory and staging area (which files
are modified, staged, or untracked).
4. git add <file(s)> / git add .: Adds changes from the working directory to the staging
area.
5. git commit -m "Your commit message": Records the staged changes to the
repository. The commit message should be descriptive.
▪ git diff: Shows changes in the working directory not yet staged.
▪ git diff --staged: Shows changes in the staging area not yet committed.
10. git fetch: Downloads commits, files, and refs from a remote repository into your
local repository, but does not merge them into your working branch.
• git branch:
• git checkout -b <new_branch_name>: Creates a new branch and switches to it. (Shorthand
for git branch <new_branch_name> then git checkout <new_branch_name>).
• git merge <branch_to_merge>: Integrates changes from a specified branch into the current
branch.
o Fast-Forward Merge: If the target branch has no new commits since the source
branch branched off, Git simply moves the pointer forward.
o Three-Way Merge: If both branches have diverged, Git creates a new "merge
commit" that combines the changes from both.
o Concept: Rewrites commit history. It takes your commits from your current branch
and reapplies them one by one on top of the specified <base_branch>.
o When to Use: To incorporate changes from the main branch into your feature branch
before merging, making the history cleaner for a later fast-forward merge.
• Common Strategies:
o Git Flow: A more complex, strict branching model with long-running branches
(master, develop) and supporting branches (feature, release, hotfix). Good for larger,
regulated projects.
o GitHub Flow / GitLab Flow: Simpler, lightweight strategies often centered around
main (or master) branch. Features are developed on short-lived branches, merged
into main, and then main is directly deployed. Preferred for continuous delivery and
smaller teams.
• What is a Conflict?
o Occurs when Git tries to merge two branches, and the same line(s) of code (or the
same file) have been modified differently in both branches. Git cannot automatically
decide which change to keep.
• Resolution Process:
1. Git will mark the conflicting sections in the file using special markers (<<<<<<<,
=======, >>>>>>>).
2. You manually edit the file to resolve the conflict (decide which version to keep, or
combine them).
4. git commit -m "Merge conflict resolved" (if merging) or git rebase --continue (if
rebasing).
• =======
Both commands are used to undo changes, but they do so in fundamentally different ways.
o Concept: Creates a new commit that undoes the changes introduced by the specified
commit. It does not rewrite history.
o Effect: The original commit still exists in the history, but its effects are cancelled out
by the new revert commit.
o When to Use: When you need to undo changes on a public/shared branch (e.g.,
main, develop) because it preserves the history and doesn't affect other developers'
clones.
o Analogy: You made a mistake, so you wrote a new instruction to undo the mistake,
but the original instruction is still on the record.
o Concept: Moves the HEAD pointer (and potentially the branch pointer and
index/working directory) to a specified commit. It effectively rewrites history by
discarding subsequent commits.
o Modes:
▪ --soft: Moves HEAD and the current branch pointer to the target commit.
The changes from the "undone" commits are kept in the staging area.
▪ --mixed (Default): Moves HEAD and the current branch pointer. The changes
are kept in the working directory (unstaged).
▪ --hard: Moves HEAD and the current branch pointer. Discards all changes
(from working directory, staging area, and commits) from the target commit
onwards. DANGEROUS! Use with extreme caution as data can be lost.
o When to Use: To undo changes on your local, private feature branches before you
have pushed them to a remote repository. It keeps your history clean and linear.
o Analogy: You erased the mistake from the record as if it never happened.
• Summary Table:
• Concept: Temporarily saves changes that you don't want to commit immediately, allowing
you to switch branches or perform other operations without committing incomplete work.
• Use Case:
o You're working on a feature, and a high-priority bug needs fixing on main. You don't
want to commit incomplete work. Stash your changes, switch to main, fix the bug,
then come back to your feature branch and reapply the stash.
• Commands:
o git stash save "message" / git stash: Saves your current uncommitted changes (both
staged and unstaged) to a temporary stash. Your working directory becomes clean.
o git stash apply: Applies the latest stash back to your working directory, but keeps the
stash in the stash list.
o git stash pop: Applies the latest stash and then removes it from the stash list. (More
common)
o git stash drop: Removes a specific stash from the stash list.
8.7. .gitignore
• Concept: A text file (.gitignore) placed in the root of your repository that tells Git which files
or directories to ignore and not track.
• Syntax:
o # for comments
No problem, let's dive into "9. Project Management Tools & Methodologies" in detail. For a 3-year
experienced Java developer, understanding these concepts isn't just about coding; it's about how
software projects are organized, delivered, and how teams collaborate effectively. You'll likely be part
of, or even lead, aspects of these processes.
This topic covers the frameworks and tools used to plan, execute, and monitor software
development projects. The goal is to deliver high-quality software efficiently and effectively.
Agile is an iterative and incremental approach to project management and software development
that helps teams deliver value to their customers faster and with fewer headaches. It emphasizes
flexibility, collaboration, and rapid response to change.
• Key Characteristics:
o Iterative & Incremental: Projects are broken into small, manageable iterations
(sprints/cycles) where a small piece of working software is delivered.
o Self-Organizing Teams: Teams are empowered to decide how to best achieve their
goals.
o Adaptive Planning: Plans are flexible and evolve as requirements become clearer.
o Continuous Improvement: Teams regularly reflect on their work and find ways to
improve.
o Scrum:
▪ Sprint Review: (End of Sprint) Inspects the Increment and adapts the
Product Backlog if needed. Stakeholders provide feedback.
▪ Key Artifacts:
o Kanban:
▪ Key Principles:
▪ Visualize the workflow (Kanban board with columns like "To Do," "In
Progress," "Done").
▪ Limit Work in Progress (WIP) to prevent bottlenecks and ensure
focus.
▪ Use Case: Ideal for maintenance teams, support teams, or workflows with
unpredictable demand where continuous flow is more important than fixed
iterations.
Cadence Time-boxed sprints (e.g., 2-4 weeks) Continuous flow, no fixed iterations
Artifacts Product Backlog, Sprint Backlog, Increment Kanban Board, WIP Limits, Flow metrics
Change Changes discouraged within a sprint Changes can be introduced at any time
Deliver potentially shippable increment per Improve flow, reduce lead time, deliver
Goals
sprint continuously
WIP
Implicit (via Sprint Planning) Explicitly defined on the board
Limits
Export to Sheets
These tools facilitate the implementation of Agile and other methodologies, providing platforms for
task management, collaboration, and tracking.
• Jira:
▪ Issue Types: Supports various issue types like Stories, Tasks, Bugs, Epics.
o Use Case: Widely used in software development for tracking features, bugs, tasks,
and managing Agile projects.
• Trello:
o Features:
o Use Case: Excellent for small teams, personal task management, or simple projects
where visual organization and ease of use are priorities. Less suited for complex
enterprise-level projects requiring detailed reporting and strict workflows.
• Confluence:
o Type: Collaboration and knowledge management software, often used with Jira.
o Features:
o Use Case: Ideal for creating and sharing project documentation, team wikis, decision
records, and knowledge management within an organization.
o General-purpose project management and work management tools that can often
be configured to support Agile methodologies. They offer varying levels of features,
complexity, and integrations, catering to different team sizes and needs.
• Waterfall Model:
o Concept: A traditional, linear, sequential approach where each phase (Requirements,
Design, Implementation, Testing, Deployment, Maintenance) must be completed
before the next phase begins.
o Use Case: Rarely used for complex software projects today, sometimes for small,
well-defined, low-risk projects.
o Impact on PM: Fosters a culture of shared responsibility and faster feedback loops.
For a 3-year experienced developer, you should be able to articulate the advantages of Agile,
specifically Scrum, and discuss the roles, events, and artifacts. You should also be familiar with the
practical application of tools like Jira, Trello, and Confluence, having likely used them in your previous
roles. Understanding the "why" behind these methodologies and tools, and how they contribute to
successful software delivery, is as important as knowing their features.