Spring Interview Playbook

 

1️⃣ Core Spring


What is Spring Framework?

Answer:

  • Lightweight, open-source Java framework for enterprise applications

  • Based on IoC (Inversion of Control) and DI (Dependency Injection)

  • Promotes loose coupling and testability

  • Modular architecture (Core, AOP, JDBC, ORM, MVC, Security)

  • Eliminates heavy XML configuration (supports annotations & Java config)

Real-time example:

  • In a banking app → Service layer depends on Repository

  • Spring injects repository into service automatically


What is Inversion of Control (IoC)?

Answer:

  • Control of object creation is given to Spring container

  • Developer no longer uses new keyword for dependencies

  • Container manages:

    • Bean creation

    • Dependency wiring

    • Lifecycle

    • Configuration

Without IoC:

UserService service = new UserService();

With IoC:

@Autowired
UserService service;

Real-time example:

  • Large e-commerce app → 200+ services

  • IoC avoids manual object creation chaos


What is Dependency Injection (DI)?

Answer:

  • Technique to inject required dependencies into a class

  • Achieves loose coupling

  • Makes unit testing easier

Types of DI

  • Constructor Injection (Recommended)

  • Setter Injection

  • Field Injection (Avoid in production)

Why Constructor Injection?

  • Makes dependencies mandatory

  • Supports immutability

  • Easy for unit testing (Mockito)

Example:

@Service
class OrderService {
private final PaymentService paymentService;

public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}

Real-time example:

  • OrderService depends on PaymentService

  • Spring injects automatically


What is a Spring Bean?

Answer:

  • Any object managed by Spring IoC container

  • Created, configured, and destroyed by container

Ways to define beans

  • @Component

  • @Service

  • @Repository

  • @Controller

  • @Bean (Java config)

  • XML configuration (legacy)

Real-time example:

  • UserService

  • EmailService

  • ProductRepository


What is Spring Bean Scopes?

Spring defines lifecycle and visibility of beans.


1. Singleton (Default)

  • One instance per Spring container

  • Shared across entire application

  • Thread-safe design required

Used in:

  • Service layer

  • DAO layer

  • Utility classes

Example:

@Scope("singleton")
@Service
class UserService {}

Real-time:

  • PaymentService shared by all users


2. Prototype

  • New object every time requested

  • Spring manages creation only (not destruction)

  • Suitable for stateful objects

Used in:

  • Report generator

  • Temporary calculation objects

@Scope("prototype")
@Component
class ReportBuilder {}

3. Request Scope (Web Apps)

  • One bean per HTTP request

  • Destroyed after request completes

Used in:

  • Request-specific form data

  • API request context


4. Session Scope

  • One bean per HTTP session

  • Lives until session expires

Used in:

  • Shopping cart

  • Logged-in user details


5. Application Scope

  • One per ServletContext

  • Shared across entire web application

Used in:

  • Application cache

  • Global settings


6. WebSocket Scope

  • One bean per WebSocket session

  • Used in chat applications


Scope Interview Follow-up Points

  • Singleton is NOT same as GoF Singleton

  • Singleton beans must be stateless

  • Prototype injected into Singleton → creates problem (use ObjectFactory)


What is @Component, @Service, @Repository, @Controller?

Answer:

All are stereotype annotations.

  • @Component → Generic bean

  • @Service → Business logic

  • @Repository → DAO (adds exception translation)

  • @Controller → MVC controller

Real-time:

  • Layered architecture separation


What is @Autowired?

Answer:

  • Automatically injects dependency

  • Works on constructor, setter, field

  • By default injects by type

  • Use @Qualifier if multiple beans

Example:

@Autowired
@Qualifier("stripePayment")
PaymentService paymentService;

What is Spring AOP?

Answer:

Aspect-Oriented Programming separates cross-cutting concerns.

Cross-cutting concerns:

  • Logging

  • Security

  • Transaction management

  • Monitoring

Key Terms:

  • Aspect → LoggingAspect

  • Advice → Before, After, Around

  • JoinPoint → Method execution

  • Pointcut → Expression

Example:

@Aspect
@Component
class LoggingAspect {
@Before("execution(* com.app.service.*.*(..))")
public void log() {
System.out.println("Method called");
}
}

Real-time:

  • Logging every service method without modifying code


What is Bean Lifecycle?

Phases:

  1. Bean instantiation

  2. Dependency injection

  3. @PostConstruct

  4. Ready for use

  5. @PreDestroy

  6. Destroy

Customization:

  • InitializingBean

  • DisposableBean

Real-time:

  • Opening DB connection on init

  • Closing resources on destroy


What is BeanFactory vs ApplicationContext?

Both are Spring IoC containers, but ApplicationContext is a more advanced version of BeanFactory.


BeanFactory

  • Basic IoC container

  • Responsible for:

    • Bean creation

    • Dependency injection

  • Beans loaded lazily (created only when requested)

  • Does NOT support:

    • Automatic AOP integration

    • Event publishing

    • Internationalization (i18n)

  • Lightweight and minimal

Example

BeanFactory factory =
new XmlBeanFactory(new ClassPathResource("beans.xml"));

MyService service = factory.getBean(MyService.class);

Used rarely in modern applications.


ApplicationContext

  • Advanced container (extends BeanFactory)

  • Provides all BeanFactory features +

    • AOP support

    • Event handling

    • Internationalization (MessageSource)

    • Environment & profiles

    • Annotation-based configuration

  • Beans loaded eagerly by default (singleton beans at startup)

  • Fully integrated with Spring Boot

Example

ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);

MyService service = context.getBean(MyService.class);

Comparison Table

BeanFactoryApplicationContext
Basic IoC containerAdvanced container
Lazy loadingEager loading (default)
No built-in AOPSupports AOP
No event systemSupports events
Minimal featuresEnterprise-ready features

Real-Time Usage

  • ApplicationContext is used in almost all real-world applications.

  • Spring Boot automatically creates an ApplicationContext.

  • BeanFactory is mostly for:

    • Memory-constrained environments

    • Understanding core IoC concepts


Summary

BeanFactory is the basic container with lazy loading.
ApplicationContext extends BeanFactory and adds enterprise features like AOP, events, profiles, and is used in almost all real applications.


What is @Configuration and @Bean?

Answer:

  • @Configuration → Marks config class

  • @Bean → Defines custom bean

Example:

@Configuration
class AppConfig {

@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}

Real-time:

  • Third-party libraries as beans


What is Circular Dependency?

Answer:

A circular dependency occurs when:

  • Bean A depends on Bean B

  • Bean B depends on Bean A

This creates a dependency cycle.


Example (Constructor Injection – Fails)

@Service
class A {
private final B b;

public A(B b) {
this.b = b;
}
}

@Service
class B {
private final A a;

public B(A a) {
this.a = a;
}
}

Result:

  • Spring tries to create A → needs B

  • To create B → needs A

  • Fails with BeanCurrentlyInCreationException


Setter Injection – Works (Not Recommended)

@Service
class A {
private B b;

@Autowired
public void setB(B b) {
this.b = b;
}
}

@Service
class B {
private A a;

@Autowired
public void setA(A a) {
this.a = a;
}
}

Why it works:

  • Spring first creates beans without dependencies

  • Injects dependencies afterward

  • Breaks creation cycle

Why not recommended:

  • Hides poor design

  • Allows partially initialized beans

  • Encourages tight coupling

  • Makes dependencies optional (not enforced)


Proper Solution (Refactor Design)

Instead of A and B depending on each other, extract common logic into a third service.

Refactored Design

@Service
class CommonService {
}

@Service
class A {
private final CommonService commonService;

public A(CommonService commonService) {
this.commonService = commonService;
}
}

@Service
class B {
private final CommonService commonService;

public B(CommonService commonService) {
this.commonService = commonService;
}
}

Now:

  • A → CommonService

  • B → CommonService

  • No circular dependency


Interview Summary

  • Circular dependency = two beans depend on each other

  • Constructor injection fails

  • Setter injection works but is bad design

  • Best solution → redesign architecture and remove tight coupling


What is Profiles in Spring?

Answer:

  • Used for environment-specific configuration

  • Allows different beans and properties to be loaded based on the active environment

  • Helps separate configuration without changing code

  • Common real-time environment names:

    • local

    • dev

    • qa

    • uat

    • staging

    • prod

Example Using @Profile

@Profile("dev")
@Component
class DevDataSource {
}

@Profile("prod")
@Component
class ProdDataSource {
}

Only the bean matching the active profile will be loaded.

How to Activate Profile

In application.properties

spring.profiles.active=dev

Or via command line

--spring.profiles.active=prod

Profile-Specific Property Files

  • application-local.properties

  • application-dev.properties

  • application-qa.properties

  • application-uat.properties

  • application-staging.properties

  • application-prod.properties

Spring automatically loads the correct file based on the active profile.

Real-Time Example

  • local → Developer machine DB

  • dev → Shared development database

  • qa → QA testing database

  • uat → User acceptance testing environment

  • staging → Pre-production environment

  • prod → Production database

Interview Summary

Profiles allow loading environment-specific beans and configurations such as database URLs, logging levels, API keys, and feature flags without modifying application code.


What is @Value and @PropertySource?

Answer:

  • Used to inject values from properties files

  • @Value → Injects a specific property value

  • @PropertySource → Loads external/custom property files into Spring context

Example using @Value

@Value("${server.port}")
private int port;

Injecting custom property:

@Value("${app.name}")
private String appName;

Example using @PropertySource

@Configuration
@PropertySource("classpath:custom.properties")
class AppConfig {
}

This loads custom.properties so values can be injected using @Value.


Used In

  • Database configuration

  • API keys

  • External service URLs

  • Feature flags

Interview Summary

@Value is used to inject individual property values, while @PropertySource is used to load additional properties files into the Spring application context.


What is Environment Abstraction?

Answer:

  • Provides access to application properties

  • Gives access to active profiles

  • Allows reading system environment variables

  • Supports checking default profiles

  • Helps implement environment-based logic

Spring provides the Environment interface for this.


Example using Environment

@Autowired
private Environment env;

Read property

String dbUrl = env.getProperty("spring.datasource.url");

Get active profiles

String[] profiles = env.getActiveProfiles();

Get default profiles

String[] defaultProfiles = env.getDefaultProfiles();

Read system environment variable

String javaHome = env.getProperty("JAVA_HOME");

Check if profile is active

if (env.acceptsProfiles("prod")) {
System.out.println("Production environment");
}

Used In

  • Dynamic configuration access

  • Profile-based conditional logic

  • Reading OS environment variables

  • Feature toggles based on environment


Interview Summary

Environment abstraction allows accessing properties, profiles, and system variables programmatically using the Environment interface, enabling environment-aware application behavior.


What is Lazy Initialization?

Answer:

  • @Lazy → Bean is created only when it is first requested

  • Default singleton beans are created eagerly at startup

  • Helps reduce application startup time

  • Useful for heavy or rarely used beans

Example using @Lazy

@Lazy
@Service
class HeavyService {
}

Bean will be created only when it is first injected or accessed.

Using @Lazy on Injection

@Service
class OrderService {

private final HeavyService heavyService;

public OrderService(@Lazy HeavyService heavyService) {
this.heavyService = heavyService;
}
}

Here, HeavyService is initialized only when actually used.

Used In

  • Expensive initialization logic

  • Large configuration objects

  • Rarely used services

  • Improving startup performance

Interview Summary

Lazy initialization delays bean creation until it is needed, helping optimize startup time and resource usage.

Java Interview Coding Challenges

 

1. Reverse a String

Definition: This program reverses a string manually without using built-in reverse methods.

public class ReverseString {
public static void main(String[] args) {

String str = "Java"; // Original string

char[] arr = str.toCharArray(); // Convert string to character array

int left = 0, right = arr.length - 1;

// Swap characters from start and end moving towards center
while (left < right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;

left++;
right--;
}

System.out.println(new String(arr));
// Output: avaJ (Original: Java → Reversed: avaJ)
}
}

2. Find Duplicate Elements in Array

Definition: This program finds elements that appear more than once in an array.

import java.util.*;

public class FindDuplicates {
public static void main(String[] args) {

int[] arr = {1, 2, 3, 2, 4, 1};

Set<Integer> seen = new HashSet<>(); // Stores unique elements
Set<Integer> duplicates = new HashSet<>(); // Stores duplicate elements

for (int num : arr) {
if (!seen.add(num)) { // add() returns false if element already exists
duplicates.add(num);
}
}

System.out.println(duplicates);
// Output: [1, 2] (These numbers appeared more than once)
}
}

3. First Non-Repeated Character

Definition: This program finds the first character in a string that appears only once.

import java.util.*;

public class FirstNonRepeated {
public static void main(String[] args) {

String str = "aabbcdde";

Map<Character, Integer> map = new LinkedHashMap<>();
// LinkedHashMap maintains insertion order

// Count frequency of each character
for (char c : str.toCharArray()) {
map.put(c, map.getOrDefault(c, 0) + 1);
}

// Find first character with frequency 1
for (var entry : map.entrySet()) {
if (entry.getValue() == 1) {
System.out.println(entry.getKey());
break;
}
}

// Output: c (First character that appears only once)
}
}

4. Check if Two Strings Are Anagram

Definition: This program checks whether two strings contain the same characters in different order.

import java.util.Arrays;

public class AnagramCheck {
public static void main(String[] args) {

String s1 = "listen";
String s2 = "silent";

char[] a = s1.toCharArray();
char[] b = s2.toCharArray();

Arrays.sort(a); // Sort characters alphabetically
Arrays.sort(b);

System.out.println(Arrays.equals(a, b));
// Output: true (Both contain same characters)
}
}

5. Implement LRU Cache

Definition: This program implements Least Recently Used (LRU) cache which removes the least recently accessed element when capacity is exceeded.

import java.util.*;

class LRUCache<K, V> extends LinkedHashMap<K, V> {

private final int capacity;

LRUCache(int capacity) {
super(capacity, 0.75f, true);
// true → maintains access order (important for LRU)
this.capacity = capacity;
}

protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
// Remove oldest entry if capacity exceeded
}
}

public class TestLRU {
public static void main(String[] args) {

LRUCache<Integer, String> cache = new LRUCache<>(3);

cache.put(1, "A");
cache.put(2, "B");
cache.put(3, "C");

cache.get(1); // Access 1 → makes 2 least recently used
cache.put(4, "D"); // Removes 2

System.out.println(cache);

// Output: {3=C, 1=A, 4=D}
// 2 was removed because it was least recently used
}
}

6. Thread-Safe Singleton

Definition: This ensures only one instance of a class is created even in multithreaded environment.

class Singleton {

private static volatile Singleton instance;
// volatile ensures visibility across threads

private Singleton() {} // private constructor

public static Singleton getInstance() {

if (instance == null) {

synchronized (Singleton.class) {

if (instance == null) {
instance = new Singleton();
}
}
}

return instance;
}
}

7. Producer-Consumer Problem

Definition: One thread produces data while another consumes it using a shared blocking queue.

import java.util.concurrent.*;

public class ProducerConsumer {
public static void main(String[] args) {

BlockingQueue<Integer> queue =
new ArrayBlockingQueue<>(5);
// Capacity = 5

Runnable producer = () -> {
try {
for (int i = 1; i <= 5; i++) {
queue.put(i); // blocks if queue is full
System.out.println("Produced: " + i);
}
} catch (Exception e) {}
};

Runnable consumer = () -> {
try {
for (int i = 1; i <= 5; i++) {
System.out.println("Consumed: " + queue.take());
// blocks if queue is empty
}
} catch (Exception e) {}
};

new Thread(producer).start();
new Thread(consumer).start();
}

/*
Sample Output (order may vary):
Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
...
*/
}

8. Detect Cycle in Linked List

Definition: This checks whether a linked list contains a loop using Floyd’s Cycle Detection algorithm.

class Node {
int data;
Node next;
Node(int data) { this.data = data; }
}

public class DetectCycle {

static boolean hasCycle(Node head) {

Node slow = head; // moves 1 step
Node fast = head; // moves 2 steps

while (fast != null && fast.next != null) {

slow = slow.next;
fast = fast.next.next;

if (slow == fast)
return true; // cycle detected
}

return false; // no cycle
}
}

9. Longest Substring Without Repeating Characters

Definition: Finds the length of the longest substring without duplicate characters.

import java.util.*;

public class LongestSubstring {
public static void main(String[] args) {

String s = "abcabcbb";

Set<Character> set = new HashSet<>();
int left = 0, max = 0;

for (int right = 0; right < s.length(); right++) {

// Remove characters until no duplicate
while (!set.add(s.charAt(right))) {
set.remove(s.charAt(left++));
}

max = Math.max(max, right - left + 1);
}

System.out.println(max);
// Output: 3 (Substring: "abc")
}
}

10. Group Anagrams

Definition: Groups words that are rearrangements of each other.

import java.util.*;

public class GroupAnagrams {
public static void main(String[] args) {

String[] words = {"eat", "tea", "tan", "ate", "nat", "bat"};

Map<String, List<String>> map = new HashMap<>();

for (String word : words) {

char[] chars = word.toCharArray();
Arrays.sort(chars); // Sort letters to create key
String key = new String(chars);

map.computeIfAbsent(key, k -> new ArrayList<>()).add(word);
}

System.out.println(map.values());

/*
Output:
[[eat, tea, ate], [tan, nat], [bat]]
*/
}
}


11. Write Your Own Immutable Class

Definition: This class is immutable, meaning its state cannot be changed after object creation.

final class Person {

private final String name; // final → cannot be reassigned
private final List<String> skills; // final reference

public Person(String name, List<String> skills) {
this.name = name;
this.skills = new ArrayList<>(skills);
// defensive copy to prevent external modification
}

public String getName() {
return name;
}

public List<String> getSkills() {
return new ArrayList<>(skills);
// return copy to maintain immutability
}
}

/*
Key Rules of Immutable Class:
1. Class should be final
2. Fields should be private and final
3. No setters
4. Return copies of mutable objects
*/

12. Implement Custom Thread Pool

Definition: This creates a simple thread pool that reuses worker threads to execute submitted tasks.

import java.util.concurrent.*;

class SimpleThreadPool {

private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// Task queue

public SimpleThreadPool(int nThreads) {
for (int i = 0; i < nThreads; i++) {
new Worker().start(); // Start worker threads
}
}

public void submit(Runnable task) {
queue.offer(task); // Add task to queue
}

class Worker extends Thread {
public void run() {
while (true) {
try {
queue.take().run();
// take() blocks until task available
} catch (Exception ignored) {}
}
}
}
}

13. Design Rate Limiter (Fixed Window)

Definition: Limits the number of requests allowed within a specific time window.

import java.util.concurrent.atomic.AtomicInteger;

class RateLimiter {

private final int limit; // max allowed requests
private final long windowSizeMs; // time window size
private long windowStart;
private AtomicInteger count = new AtomicInteger(0);

public RateLimiter(int limit, long windowSizeMs) {
this.limit = limit;
this.windowSizeMs = windowSizeMs;
this.windowStart = System.currentTimeMillis();
}

public synchronized boolean allowRequest() {

long now = System.currentTimeMillis();

// Reset window if time exceeded
if (now - windowStart > windowSizeMs) {
windowStart = now;
count.set(0);
}

return count.incrementAndGet() <= limit;
}
}

14. Find Kth Largest Element

Definition: Finds the Kth largest element using a Min Heap (PriorityQueue).

import java.util.*;

public class KthLargest {

public static int findKthLargest(int[] nums, int k) {

PriorityQueue<Integer> pq = new PriorityQueue<>();
// Min Heap

for (int n : nums) {
pq.offer(n);

if (pq.size() > k)
pq.poll(); // Remove smallest element
}

return pq.peek(); // Kth largest
}
}

/*
Example:
nums = {3,2,1,5,6,4}, k = 2
Output: 5
*/

15. Implement Observer Pattern

Definition: Implements Observer design pattern where observers are notified when subject changes.

import java.util.*;

interface Observer {
void update(String message);
}

class Subject {

private List<Observer> observers = new ArrayList<>();

public void addObserver(Observer o) {
observers.add(o);
}

public void notifyObservers(String msg) {

for (Observer o : observers)
o.update(msg);
}
}

/*
Used when:
- Event notification systems
- Publish/Subscribe models
*/

16. Find Second Highest Salary

Definition: Finds second highest distinct salary using Java Stream API.

import java.util.*;

class Employee {
String name;
int salary;

Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
}

public class SecondHighest {

public static void main(String[] args) {

List<Employee> list = List.of(
new Employee("A", 5000),
new Employee("B", 7000),
new Employee("C", 6000)
);

int salary = list.stream()
.map(e -> e.salary)
.distinct() // remove duplicates
.sorted(Comparator.reverseOrder())
.skip(1) // skip highest
.findFirst()
.orElse(0);

System.out.println(salary);
// Output: 6000
}
}

17. Design Parking Lot (Basic LLD)

Definition: Demonstrates basic object-oriented design for parking system.

class Car {}

class ParkingSpot {

private boolean occupied;

public boolean park() {

if (!occupied) {
occupied = true;
return true; // Car parked successfully
}

return false; // Spot already occupied
}
}

18. Implement Custom Comparator Sorting

Definition: Sorts objects by multiple fields using Comparator.

list.sort(Comparator
.comparing(Employee::getName)
.thenComparing(Employee::getSalary));

/*
First sorts by name,
If names equal → sorts by salary.
*/

19. Implement Read-Write Lock Example

Definition: Uses ReadWriteLock to allow multiple readers but only one writer.

import java.util.concurrent.locks.*;

class SharedResource {

private final ReadWriteLock lock = new ReentrantReadWriteLock();

public void read() {

lock.readLock().lock(); // acquire read lock

try {
System.out.println("Reading...");
} finally {
lock.readLock().unlock(); // release read lock
}
}
}

/*
Multiple threads can read simultaneously,
but write lock is exclusive.
*/

20. Word Frequency Counter from File

Definition: Reads a file and counts how many times each word appears.

import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class WordFrequency {

public static void main(String[] args) throws Exception {

Map<String, Long> frequency =
Files.lines(Path.of("file.txt"))
.flatMap(line -> Arrays.stream(line.split("\\W+")))
.map(String::toLowerCase)
.filter(word -> !word.isBlank())
.collect(Collectors.groupingBy(
w -> w,
Collectors.counting()
));

System.out.println(frequency);

/*
Example Output:
{java=3, interview=2, code=1}
*/
}
}