Skip to content

What Spring Boot Concepts Should You Know for Java Interviews?

I thought I knew Spring Boot. Then I walked into an interview and got asked: “What happens between @PostConstruct and afterPropertiesSet?”

Blank stare. The interviewer smiled. “Be aware of its nuances,” he said, “they will catch you in an interview over it.”

That moment taught me the gap between using Spring Boot and being interview-ready for it. Here are the five areas where I got caught—and how to avoid the same traps.

1. Bean Lifecycle: The Interview Favorite

The bean lifecycle isn’t just academic trivia. Interviewers test it because real-world debugging often requires knowing exactly when initialization happens.

Here’s the complete lifecycle:

Instantiation
Dependency Injection
BeanNameAware.setBeanName()
BeanPostProcessor.postProcessBeforeInitialization()
@PostConstruct
InitializingBean.afterPropertiesSet()
BeanPostProcessor.postProcessAfterInitialization()
Ready for Use
@PreDestroy (on shutdown)
DisposableBean.destroy()

Interview Trap: @PostConstruct runs BEFORE afterPropertiesSet. They’re not interchangeable.

Here’s a bean demonstrating all lifecycle callbacks:

BeanLifecycleDemo.java
@Component
public class BeanLifecycleDemo implements
BeanNameAware, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(BeanLifecycleDemo.class);
@Override
public void setBeanName(String name) {
logger.info("1. BeanNameAware: My name is {}", name);
}
@PostConstruct
public void postConstruct() {
logger.info("2. @PostConstruct: Dependencies injected");
}
@Override
public void afterPropertiesSet() throws Exception {
logger.info("3. InitializingBean: Properties set");
}
@PreDestroy
public void preDestroy() {
logger.info("4. @PreDestroy: About to destroy");
}
@Override
public void destroy() throws Exception {
logger.info("5. DisposableBean: Destroying");
}
}

Why interviewers ask this: When your @PostConstruct method fails, you need to know whether it’s a dependency issue or a lifecycle timing problem. They’re testing your debugging instincts.

2. Dependency Injection: Three Patterns, One Clear Winner

I used field injection everywhere. “@Autowired on the field—done.” Then the interviewer asked me to unit test a field-injected service.

“Can’t mock that without reflection,” I realized.

Constructor Injection (Preferred)

ConstructorInjection.java
@Service
public class OrderService {
private final PaymentService paymentService;
private final NotificationService notificationService;
// @Autowired optional since Spring 4.3 for single constructor
public OrderService(PaymentService paymentService,
NotificationService notificationService) {
this.paymentService = paymentService;
this.notificationService = notificationService;
}
}

Pros: Immutable dependencies, easy to test, explicit requirements Cons: More verbose for many dependencies

Setter Injection (Optional Dependencies)

SetterInjection.java
@Service
public class EmailService {
private TemplateEngine templateEngine;
@Autowired
public void setTemplateEngine(TemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
}

Pros: Flexible, allows optional dependencies, can be changed at runtime Cons: Mutable, risk of NullPointerException if not set

Field Injection (Avoid)

FieldInjection.java
@Service
public class BadService {
@Autowired
private Dependency dependency; // Hard to test, hard to see requirements
}

Pros: Least code Cons: Hidden dependencies, impossible to unit test without Spring context or reflection

| Type | Immutable | Testable | Explicit | Recommendation |
|-------------|-----------|----------|----------|-------------------|
| Constructor | ✓ | ✓ | ✓ | Preferred |
| Setter | ✗ | ✓ | ✓ | Optional deps |
| Field | ✗ | ✗ | ✗ | Avoid |

Interview tip: Since Spring 4.3, you can omit @Autowired on constructors if there’s only one constructor. They might ask why that works.

3. Stereotype Annotations: Not Just Semantics

“@Repository is just a semantic marker for the data layer, right?”

Wrong. That answer cost me points.

StereotypeAnnotations.java
// Generic component - auto-detected by @ComponentScan
@Component
public class UtilityHelper { }
// Service layer - semantically marks business logic
@Service
public class UserService { }
// Data layer - HAS SPECIAL BEHAVIOR
@Repository
public class UserRepository { }
// Web views - returns view names
@Controller
public class PageController {
public String home() { return "home"; }
}
// REST APIs - @Controller + @ResponseBody
@RestController
public class ApiController {
@GetMapping("/api/data")
public Data getData() { return new Data(); }
}

The @Repository trap: It doesn’t just mark a bean as a data access object. It enables exception translation—converting SQLException to Spring’s DataAccessException hierarchy.

| Annotation | Layer | Special Behavior |
|---------------|------------|-------------------------------------|
| @Component | Generic | Auto-detected |
| @Service | Business | Semantically marks service |
| @Repository | Data | Exception translation (!) |
| @Controller | Web Views | Returns view names |
| @RestController| REST | @Controller + @ResponseBody |

Why interviewers test this: They want to know if you understand the framework’s conventions and why they exist. Using @Service on a DAO isn’t wrong, but it shows you don’t understand Spring’s layered architecture.

4. Bean Scopes: The Prototype-in-Singleton Trap

“What happens when you inject a prototype-scoped bean into a singleton?”

“New instance each time,” I said confidently.

Wrong. You get ONE instance, injected once, reused forever.

BeanScopes.java
@Scope("prototype")
@Component
public class PrototypeBean {
private String state = "initial";
}
@Scope("singleton") // default
@Component
public class SingletonBean {
@Autowired
private PrototypeBean prototypeBean; // Same instance forever!
public void doSomething() {
prototypeBean.setState("changed"); // Affects all callers
}
}

Correct Approaches

Option 1: Use ObjectProvider

ObjectProviderSolution.java
@Component
public class SingletonBean {
@Autowired
private ObjectProvider<PrototypeBean> prototypeProvider;
public void doSomething() {
PrototypeBean prototype = prototypeProvider.getObject(); // New instance!
prototype.setState("fresh state");
}
}

Option 2: Use Proxy Mode

ProxyModeSolution.java
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PrototypeBean { }
| Scope | Lifecycle | Use Case | Gotcha |
|----------|----------------------------|-----------------------------|---------------------------|
| Singleton| One per container (default)| Stateless services | Shared state = bugs |
| Prototype| New per injection | Stateful beans | Injection = one instance |
| Request | One per HTTP request | Request-scoped data | Needs RequestContext |
| Session | One per user session | Shopping carts, user prefs | Cluster replication |

Why this matters: In production, state leakage between requests causes bizarre bugs. Interviewers want to know you understand lifecycle implications.

5. REST API Mastery: Beyond @GetMapping

Interviewers don’t just want to see you can write endpoints. They test whether you understand the conventions and shortcuts.

RestControllerDemo.java
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@Valid @RequestBody CreateUserRequest request) {
return userService.create(request);
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
return userService.update(id, request);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}

Key interview points:

  • @GetMapping = @RequestMapping(method = RequestMethod.GET)
  • @PathVariable extracts values from the URL
  • @RequestBody deserializes JSON to objects
  • @ResponseStatus overrides default HTTP status
  • @Valid triggers bean validation

Handling Multiple Beans of the Same Type

MultipleBeansResolution.java
public interface PaymentProcessor { void process(); }
@Component
public class StripeProcessor implements PaymentProcessor { }
@Component
public class PayPalProcessor implements PaymentProcessor { }
@Service
public class PaymentService {
// Option 1: @Primary on one implementation
@Autowired
private PaymentProcessor paymentProcessor;
// Option 2: @Qualifier
@Autowired
@Qualifier("stripeProcessor")
private PaymentProcessor stripeProcessor;
// Option 3: Named injection
@Autowired
private PaymentProcessor payPalProcessor; // Matches bean name
}

Mark one as primary:

PrimaryBean.java
@Component
@Primary
public class StripeProcessor implements PaymentProcessor { }

The Self-Invocation Trap

One more gotcha that catches experienced developers: transaction self-invocation.

TransactionSelfInvocation.java
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
validateOrder(order); // Transaction NOT applied!
saveOrder(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void validateOrder(Order order) {
// Self-invocation bypasses proxy - no new transaction!
}
}

Why: Spring’s @Transactional works through proxies. Self-invocation doesn’t go through the proxy.

Solution: Inject yourself or refactor:

TransactionSolution.java
@Service
public class OrderService {
@Autowired
private OrderService self; // Inject self
@Transactional
public void placeOrder(Order order) {
self.validateOrder(order); // Goes through proxy!
saveOrder(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void validateOrder(Order order) {
// Now creates new transaction
}
}

Interview Mistakes to Avoid

From my interview experience, here are the mistakes that cost me points:

  1. Lifecycle confusion: “What runs after @PostConstruct?” → afterPropertiesSet, not @PreDestroy
  2. DI preference: “Field injection is simplest” → Should prefer constructor injection
  3. Scope misunderstanding: “Prototype in Singleton gets new instance each call” → Gets ONE instance
  4. @Repository misconception: “Just semantic” → Has exception translation behavior
  5. REST mapping: “Use @RequestMapping for everything” → Prefer specific annotations (@GetMapping, @PostMapping)

Why These Concepts Matter

Interviewers don’t ask about bean lifecycle to torture you. They ask because:

  • Bug diagnosis: When your @PostConstruct fails, knowing the lifecycle tells you if it’s a wiring issue or initialization timing
  • Architecture decisions: Constructor vs field injection affects testability and immutability
  • Production issues: Prototype-in-singleton state leakage causes mysterious bugs
  • Code quality: Using the right stereotype shows you understand layered architecture
  • Team collaboration: Consistent patterns make codebases maintainable

“You’ll need to know how to navigate a project, import things safely, ensure bean checking and know how to rewire projects,” a senior architect told me. These concepts are the foundation.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments