Question: Explain Spring Boot project structure.
Answer: Spring Boot projects usually follow a simple folder layout: application code in src/main/java, configuration and static files in src/main/resources, and tests in src/test/java. Inside the Java code, we usually group classes by role, such as controller, service, repository, and model, so each part has one clear job.
Interview-Ready Answer: “I organize a Spring Boot project around the standard Maven or Gradle structure. My main application class sits in the root package, and under that I keep controllers, services, repositories, and domain classes in subpackages. I put configuration like application.properties in src/main/resources and tests in src/test/java. The key idea is that Spring scans the root package and its children, so the package layout directly affects which beans are discovered.”
Detailed Explanation: A Spring Boot project structure is the way your code and files are arranged so Spring can find classes, load configuration, and keep the app easy to maintain. Think of it as the “floor plan” of the application: not just where files live, but how responsibilities are separated.
src/main/java holds production code, src/main/resources holds non-code resources, and src/test/java holds tests. This is the default layout most Spring Boot tools expect.@SpringBootApplication should usually be in a top-level package like com.example.demo. That matters because @SpringBootApplication performs component scanning (searching for Spring-managed classes such as @Component, @Service, and @RestController) in its package and subpackages.controller for HTTP endpoints, service for business rules, repository for data access, and model or domain for business objects. This keeps each class focused and easier to test.application.properties or application.yml live in src/main/resources. Static assets such as CSS, JavaScript, and images go in static, while server-side templates go in templates.ProductService should usually live under the matching package in src/test/java. This makes it easier to find tests and helps Spring test slices work cleanly.Good structure reduces accidental coupling. If controllers know too much about database code, changes become risky. If packages are organized well, you can replace a repository implementation, add a new endpoint, or test a service without touching unrelated code.
| Style | Best for | Pros | Watch out |
|---|---|---|---|
| Layered | Small to medium apps | Simple, easy to explain | Can spread one feature across many folders |
| Feature-based | Larger apps | Everything for one feature stays together | Can feel unfamiliar at first |
In a layered structure, you might have controller, service, and repository packages. In a feature-based structure, you might have product, order, and customer packages, and each feature contains its own controller/service/repository classes. Feature-based structure often scales better because changes stay localized.
@SpringBootApplication.@Service and @RestController.application.properties or application.yml to configure ports, database URLs, logging, and more.src/test/java run with the same package conventions, which helps Spring load only what the test needs.There is no real algorithmic complexity to memorize here, but package scanning cost grows with the number of classes in scanned packages. In a small app that cost is tiny; in a large app, poor package layout can make startup slower and create accidental bean conflicts. A very common mistake is putting the main class in a nested package such as com.example.demo.app while controllers live in com.example.demo.web; in that case, web is a sibling package, not a child, so it may not be scanned.
Version note: Spring Boot 3 moved from javax.* to jakarta.* for Jakarta EE APIs, and it requires Java 17+. The project structure is the same idea, but older Boot 2 code may use older package names in imports.
Memory Hook: Think of the project as a house: the main class is the front door, subpackages are the rooms, resources are the utilities and furniture, and tests are the smoke detectors. If the front door is in the wrong place, Spring may not “see” the other rooms.
Real-World Example: Imagine a checkout service for an online store. The team keeps CheckoutController in com.shop.checkout.web, CheckoutService in com.shop.checkout.service, and a database repository in com.shop.checkout.repo. During a release, someone moves the main application class to com.shop to “clean up the package,” but forgets to move a new payment controller that lives in com.other.payment.
What happens? The app starts, but the payment endpoint never appears. Users get 404 errors, and logs may show that the controller bean was never created. In a worse case, a service bean is missing and startup fails with an error like UnsatisfiedDependencyException, which means Spring could not inject a required object. This is why package layout is not cosmetic: it directly controls what Spring loads.
package com.example.demo;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@SpringBootApplication
public class ProjectStructureApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectStructureApplication.class, args);
}
// In real projects this seed logic often lives in a separate configuration class.
// It shows that startup code can sit near the root while business code lives in feature packages.
@org.springframework.context.annotation.Bean
CommandLineRunner seedData(ProductRepository repository) {
return args -> {
repository.save(new Product(null, "Keyboard", 49.99));
repository.save(new Product(null, "Mouse", 19.99));
};
}
}
// Domain object: keeps the business data simple and focused.
record Product(Long id, String name, double price) {}
// Request object: what the API accepts from clients.
record CreateProductRequest(String name, double price) {}
@RestController
@RequestMapping("/products")
class ProductController {
private final ProductService productService;
ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
List<Product> all() {
return productService.findAll();
}
@GetMapping("/{id}")
Product one(@PathVariable Long id) {
return productService.findById(id);
}
@PostMapping
ResponseEntity<Product> create(@RequestBody CreateProductRequest request) {
// A small guardrail: reject clearly invalid input instead of saving bad data.
if (request.name() == null || request.name().isBlank()) {
return ResponseEntity.badRequest().build();
}
if (request.price() < 0) {
return ResponseEntity.badRequest().build();
}
Product created = productService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}
@Service
class ProductService {
private final ProductRepository productRepository;
ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
List<Product> findAll() {
return productRepository.findAll();
}
Product findById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}
Product create(CreateProductRequest request) {
return productRepository.save(new Product(null, request.name(), request.price()));
}
}
@Repository
class ProductRepository {
private final Map<Long, Product> store = new ConcurrentHashMap<>();
private final AtomicLong sequence = new AtomicLong(0);
List<Product> findAll() {
return List.copyOf(store.values());
}
java.util.Optional<Product> findById(Long id) {
return java.util.Optional.ofNullable(store.get(id));
}
Product save(Product product) {
long id = product.id() == null ? sequence.incrementAndGet() : product.id();
Product saved = new Product(id, product.name(), product.price());
store.put(id, saved);
return saved;
}
}
class ProductNotFoundException extends RuntimeException {
ProductNotFoundException(Long id) {
super("Product not found: " + id);
}
}
record ApiError(String message) {}
@RestControllerAdvice
class GlobalErrorHandler {
@ExceptionHandler(ProductNotFoundException.class)
ResponseEntity<ApiError> handleNotFound(ProductNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ApiError(ex.getMessage()));
}
}Follow-up & Tricky Questions:
@SpringBootApplication class be in the root package? A: Because Spring scans that package and its subpackages for beans. If it sits too deep or in the wrong branch, some controllers or services may not be discovered.src/main/resources? A: Configuration like application.properties, static files like CSS and images, and templates for server-side rendering. It is not for Java classes.src/test/java? A: So test code is separated from production code and can be run independently by the build tool. The package structure usually mirrors the main code to keep tests easy to navigate.application.properties is missing, does the app fail? A: Usually no; Spring Boot has defaults. The file is only needed when you want to override defaults such as the port, datasource URL, or logging level.Common Mistakes:
resources: That breaks conventions and confuses the build. Correction: keep Java in src/main/java and non-code files in src/main/resources.src/test/java.Memory Hook: “Door, rooms, utilities, detectors.” The main class is the door, packages are rooms, resources are utilities, and tests are the detectors that protect the house.
Cheat Sheet:
src/main/java = app codesrc/main/resources = config, static files, templatessrc/test/java = testsPractice Tasks:
controller/service/repository packages.order and customer.