Spring Boot & Design Patterns Interview Q&A Guide
Spring Boot & Design Patterns Interview Q&A Guide
java
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@Controller
public class WebController {
@RequestMapping("/home")
@ResponseBody
public String home() {
return "Hello World";
}
}
@RestController
public class ApiController {
@RequestMapping("/api/home")
public String home() {
return "Hello World"; // Automatically converted to JSON
}
}
java
6. What is @PathVariable?
@PathVariable is used to extract values from the URI path and bind them to method parameters.
java
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
java
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { }
@PostMapping("/users")
public User createUser(@RequestBody User user) { }
Lightweight Heavy
java
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse("NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "Something went wrong");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE age > ?1", nativeQuery = true)
List<User> findUsersOlderThan(int age);
@Modifying
@Query("UPDATE User u SET u.active = false WHERE u.id = ?1")
void deactivateUser(Long id);
}
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
existingUser.setName(updatedUser.getName());
existingUser.setEmail(updatedUser.getEmail());
return userRepository.save(existingUser);
}
}
Used to resolve ambiguity when multiple beans of the same type exist.
java
@Service
@Qualifier("emailService")
public class EmailNotificationService implements NotificationService { }
@Service
@Qualifier("smsService")
public class SMSNotificationService implements NotificationService { }
@RestController
public class NotificationController {
@Autowired
@Qualifier("emailService")
private NotificationService notificationService;
}
java
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication { }
// Or using properties
# application.properties
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfigur
properties
server.port=8081
Or programmatically:
java
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
System.setProperty("server.port", "8081");
SpringApplication.run(MyApplication.class, args);
}
}
java
@RestController
public class UserController {
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
java
@RestController
public class ClientController {
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
@GetMapping("/call-service")
public String callService() {
return restTemplate.getForObject("http://my-service/api/data", String.class);
}
}
java
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDatabaseError(DataAccessException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("DATABASE_ERROR", ex.getMessage()));
}
}
properties
# application-dev.properties
spring.datasource.url=jdbc:h2:mem:devdb
# application-prod.properties
spring.datasource.url=jdbc:mysql://prod-server:3306/proddb
java
@Profile("dev")
@Configuration
public class DevConfig { }
@Profile("prod")
@Configuration
public class ProdConfig { }
Activate profile:
bash
properties
server.servlet.context-path=/api
java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByActiveTrue();
}
@Component
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
java
# application.properties
logging.level.com.example=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
logging.file.name=app.log
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
properties
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
Insecure Secure
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
@Service
public class UserService {
private final UserRepository userRepository;
2. Setter Injection:
java
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
3. Field Injection:
java
@Autowired
private UserRepository userRepository;
java
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// Custom health check logic
return Health.up()
.withDetail("database", "available")
.build();
}
}
Dependencies selection
Project metadata
Build tool choice (Maven/Gradle)
java
@Configuration
public class AppConfig {
@Bean
@Scope("singleton") // Default scope
public UserService userService() {
return new UserService();
}
@Bean
@Scope("prototype")
public ShoppingCart shoppingCart() {
return new ShoppingCart();
}
}
java
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class MyApplication { }
48. CRUD vs JPA Repositories
CrudRepository:
java
JpaRepository:
java
java
@PostMapping("/users")
public User createUser(@RequestBody User user) { // JSON to Object
return userService.save(user); // Object to JSON (with @RestController)
}
@Controller
public class WebController {
@PostMapping("/users")
@ResponseBody // Explicitly needed with @Controller
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
@Controller
public class DataController {
@GetMapping("/data")
@ResponseBody
public List<Data> getData() {
return dataService.findAll(); // Returns JSON
}
}
54. @ComponentScan
Configures component scanning:
java
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
java
@Repository
public class CustomUserRepository {
@Autowired
private EntityManager entityManager;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true)
private String email;
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
java
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
// Product interface
interface Vehicle {
void start();
}
// Concrete products
class Car implements Vehicle {
public void start() { System.out.println("Car started"); }
}
// Factory
class VehicleFactory {
public static Vehicle createVehicle(String type) {
switch(type.toLowerCase()) {
case "car": return new Car();
case "bike": return new Bike();
default: throw new IllegalArgumentException("Unknown vehicle type");
}
}
}
// Usage
Vehicle car = VehicleFactory.createVehicle("car");
car.start();
// Thread-safe singleton
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
// Usage
DatabaseConnection db = DatabaseConnection.getInstance();
db.connect();
// Abstract Factory
interface GUIFactory {
Button createButton();
TextField createTextField();
}
// Concrete Factories
class WindowsFactory implements GUIFactory {
public Button createButton() { return new WindowsButton(); }
public TextField createTextField() { return new WindowsTextField(); }
}
// Products
interface Button { void render(); }
interface TextField { void render(); }
import java.util.*;
// Subject
class NewsAgency {
private List<Observer> observers = new ArrayList<>();
private String news;
// Observer
interface Observer {
void update(String news);
}
// Usage
Computer computer = new Computer.Builder()
.setCPU("Intel i7")
.setRAM("16GB")
.setStorage("512GB SSD")
.setGPU("NVIDIA RTX 3080")
.build();
// Target interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee
class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("Playing vlc file: " + fileName);
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedPlayer;
1. Reflection:
java
2. Serialization:
java
3. Cloning:
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2Login()
.jwt();
return http.build();
}
}
Custom Validation
java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
public @interface ValidEmail {
String message() default "Invalid email format";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
return email != null && email.matches(EMAIL_PATTERN);
}
}
// Usage
public class User {
@ValidEmail
private String email;
}
@Autowired
private UserService userService;
@MockBean
private UserRepository userRepository;
@Test
void testCreateUser() {
// Given
User user = new User("John", "[email protected]");
when(userRepository.save(any(User.class))).thenReturn(user);
// When
User result = userService.createUser(user);
// Then
assertThat(result.getName()).isEqualTo("John");
verify(userRepository).save(user);
}
}
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testGetUser() throws Exception {
User user = new User("John", "[email protected]");
when(userService.findById(1L)).thenReturn(user);
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"))
.andExpect(jsonPath("$.email").value("[email protected]"));
}
}
Caching in Spring Boot
java
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users", "products");
}
}
@Service
public class UserService {
Async Processing
java
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
@Service
public class EmailService {
@Async
public CompletableFuture<String> sendEmailAsync(String to, String subject) {
// Simulate email sending
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return CompletableFuture.completedFuture("Email sent to " + to);
}
}
Event Handling
java
// Custom Event
public class UserRegisteredEvent extends ApplicationEvent {
private final User user;
// Event Publisher
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher;
// Publish event
eventPublisher.publishEvent(new UserRegisteredEvent(this, savedUser));
return savedUser;
}
}
// Event Listener
@Component
public class UserEventListener {
@EventListener
@Async
public void handleUserRegistered(UserRegisteredEvent event) {
User user = event.getUser();
// Send welcome email
emailService.sendWelcomeEmail(user.getEmail());
}
}
Custom Health Indicators
java
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try (Connection connection = dataSource.getConnection()) {
if (connection.isValid(1)) {
return Health.up()
.withDetail("database", "Available")
.withDetail("validationQuery", "SELECT 1")
.build();
}
} catch (SQLException e) {
return Health.down()
.withDetail("database", "Unavailable")
.withDetail("error", e.getMessage())
.build();
}
return Health.down().withDetail("database", "Connection failed").build();
}
}
Transaction Management
java
@Service
@Transactional
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Transactional(propagation = Propagation.REQUIRED)
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new AccountNotFoundException("From account not found"));
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("Insufficient balance");
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
@Transactional(readOnly = true)
public List<Account> getAllAccounts() {
return accountRepository.findAll();
}
}
Custom Annotations
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
String value() default "";
}
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(logExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecution
long start = System.currentTimeMillis();
return result;
}
}
// Usage
@Service
public class UserService {
@LogExecutionTime("User creation")
public User createUser(User user) {
return userRepository.save(user);
}
}
Microservices Communication
java
// Feign Client
@FeignClient(name = "user-service", url = "http://localhost:8081")
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable Long id);
@PostMapping("/users")
User createUser(@RequestBody User user);
}
@Autowired
private UserServiceClient userServiceClient;
@CreatedDate
@Column(name = "created_date", updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
@Column(name = "last_modified_date")
private LocalDateTime lastModifiedDate;
@CreatedBy
@Column(name = "created_by", updatable = false)
private String createdBy;
@LastModifiedBy
@Column(name = "last_modified_by")
private String lastModifiedBy;
}
@Entity
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true)
private String email;
@ManyToMany
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}
// Custom Repository Implementation
@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<User> findUsersWithCustomCriteria(String name, String email) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query).getResultList();
}
}
Configuration Properties
java
@ConfigurationProperties(prefix = "app")
@Component
public class AppProperties {
// application.yml
app:
name: MyApplication
version: 1.0.0
security:
jwt-secret: mySecretKey
jwt-expiration-ms: 86400000
database:
url: jdbc:mysql://localhost:3306/mydb
username: user
password: password
max-connections: 20
@Component
public class CustomMetrics {
this.userCreationTimer = Timer.builder("user.creation.time")
.description("Time taken to create user")
.register(meterRegistry);
this.activeUsersGauge = Gauge.builder("user.active.count")
.description("Number of active users")
.register(meterRegistry, this, CustomMetrics::getActiveUserCount);
}
WebSocket Configuration
java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ChatWebSocketHandler(), "/chat")
.setAllowedOrigins("*");
}
}
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
System.out.println("Connection established: " + session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exce
// Broadcast message to all connected clients
for (WebSocketSession webSocketSession : sessions) {
if (webSocketSession.isOpen()) {
webSocketSession.sendMessage(message);
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
System.out.println("Connection closed: " + session.getId());
}
}
Scheduling
java
@Configuration
@EnableScheduling
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-");
scheduler.initialize();
return scheduler;
}
}
@Component
public class ScheduledTasks {
Command Pattern
java
// Command interface
interface Command {
void execute();
void undo();
}
// Concrete commands
class CreateUserCommand implements Command {
private UserService userService;
private User user;
private User createdUser;
@Override
public void execute() {
createdUser = userService.createUser(user);
}
@Override
public void undo() {
if (createdUser != null) {
userService.deleteUser(createdUser.getId());
}
}
}
// Invoker
class CommandInvoker {
private Stack<Command> commandHistory = new Stack<>();
Strategy Pattern
java
// Strategy interface
interface PaymentStrategy {
void pay(double amount);
}
// Concrete strategies
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card: " + cardNumber);
}
}
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal: " + email);
}
}
// Context
class PaymentContext {
private PaymentStrategy paymentStrategy;
// Template method
public final void processData() {
readData();
processData();
writeData();
}
@Override
protected void readData() {
System.out.println("Reading CSV data");
}
@Override
protected void processData() {
System.out.println("Processing CSV data");
}
@Override
protected void writeData() {
System.out.println("Writing CSV data");
}
}
@Override
protected void readData() {
System.out.println("Reading XML data");
}
@Override
protected void processData() {
System.out.println("Processing XML data");
}
@Override
protected void writeData() {
System.out.println("Writing XML data");
}
}
Decorator Pattern
java
// Component interface
interface Coffee {
String getDescription();
double getCost();
}
// Concrete component
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 2.0;
}
}
// Base decorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
// Concrete decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double getCost() {
return coffee.getCost() + 0.5;
}
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public double getCost() {
return coffee.getCost() + 0.2;
}
}
// Usage
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " costs $" + coffee.getCost());
Facade Pattern
java
// Subsystem classes
class CPU {
public void freeze() { System.out.println("CPU freezing"); }
public void jump(long position) { System.out.println("CPU jumping to " + position); }
public void execute() { System.out.println("CPU executing"); }
}
class Memory {
public void load(long position, byte[] data) {
System.out.println("Memory loading data at position " + position);
}
}
class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("Hard drive reading " + size + " bytes from " + lba);
return new byte[size];
}
}
// Facade
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
// Usage
ComputerFacade computer = new ComputerFacade();
computer.start();
Best Practices and Tips
3. Interview Tips
Understand the problem before suggesting a solution
Be able to explain trade-offs and alternatives
This comprehensive guide covers all the major Spring Boot and Design Pattern questions commonly asked
in interviews. Practice these concepts with hands-on coding to build confidence and deep understanding.