Chapter 4: REST Design & Design Patterns
These exercises cover REST API design and design pattern implementation — two very common categories of questions in Java/Spring Boot backend interviews.
Exercise 1 — Design a complete REST endpoint
"Design and implement an endpoint to create and retrieve users."
Solution
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity<UserDTO> create(@Valid @RequestBody CreateUserRequest req) {
UserDTO user = userService.create(req);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping
public ResponseEntity<Page<UserDTO>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(userService.findAll(PageRequest.of(page, size)));
}
}
The DTOs (Data Transfer Objects)
public record CreateUserRequest(
@NotBlank String name,
@Email String email
) {}
public record UserDTO(Long id, String name, String email) {}
Key points to mention in an interview
| Best Practice | Why |
|---|---|
| DTOs instead of entities | Never expose JPA entities in the API |
| @Valid | Validate server-side inputs |
| 201 Created for POST | Correct HTTP codes |
| 404 Not Found if absent | Return the correct status |
| Pagination on lists | Never return all data at once |
| Java Records for DTOs | Immutable, concise, auto-generate equals/hashCode/toString |
"I use DTOs to avoid exposing entities, @Valid for input validation, proper HTTP status codes (201 Created, 404 Not Found), and pagination for list endpoints. I use Java Records for immutable DTOs."
Exercise 2 — Design an e-commerce API
"Design the REST endpoints for an e-commerce system."
Solution
Products:
GET /api/products → Paginated list of products
GET /api/products/{id} → Product detail
POST /api/products → Create a product (ADMIN)
PUT /api/products/{id} → Update a product (ADMIN)
DELETE /api/products/{id} → Delete a product (ADMIN)
Orders:
POST /api/orders → Place an order
GET /api/orders → My orders (logged-in user)
GET /api/orders/{id} → Order detail
PATCH /api/orders/{id}/status → Update status (ADMIN)
Auth:
POST /api/auth/register → Register
POST /api/auth/login → Login (returns JWT)
POST /api/auth/refresh → Refresh token
Cart:
GET /api/cart → My cart
POST /api/cart/items → Add item to cart
PUT /api/cart/items/{id} → Update quantity
DELETE /api/cart/items/{id} → Remove from cart
Mentioned REST best practices
| Rule | Example |
|---|---|
| Plural names | /products not /product |
| HTTP verbs = actions | GET = read, POST = create, PUT = replace, PATCH = partial update, DELETE = delete |
| No verbs in URL | /api/orders not /api/getOrders |
| Nested resources | /api/cart/items (items belong to the cart) |
| Role-based security | ADMIN for sensitive operations |
Exercise 3 — Implement the Builder Pattern
"Create an immutable HttpRequest class using the Builder Pattern."
Solution
public final class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;
private HttpRequest(Builder builder) {
this.url = Objects.requireNonNull(builder.url, "URL required");
this.method = builder.method;
this.headers = Map.copyOf(builder.headers); // defensive copy
this.body = builder.body;
}
public String getUrl() { return url; }
public String getMethod() { return method; }
public Map<String, String> getHeaders() { return headers; }
public String getBody() { return body; }
public static class Builder {
private String url;
private String method = "GET";
private final Map<String, String> headers = new HashMap<>();
private String body;
public Builder url(String url) { this.url = url; return this; }
public Builder method(String m) { this.method = m; return this; }
public Builder header(String k, String v) { headers.put(k, v); return this; }
public Builder body(String b) { this.body = b; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}
Usage
HttpRequest req = new HttpRequest.Builder()
.url("https://api.sonar.com/issues")
.method("POST")
.header("Authorization", "Bearer token123")
.header("Content-Type", "application/json")
.body("{\"severity\": \"CRITICAL\"}")
.build();
Key points
| Technique | Why |
|---|---|
final class | Prevents inheritance |
final fields | Immutability |
private constructor | Enforce Builder usage |
Map.copyOf() | Defensive copy — object cannot be modified after creation |
Objects.requireNonNull() | Validate required fields |
Fluent API (return this) | Method chaining |
"Immutable object, defensive copies for collections, required fields validated in constructor. The fluent API makes the call site self-documenting."
Exercise 4 — Implement the Singleton Pattern (Thread-Safe)
"Implement a thread-safe Singleton in Java."
Modern Solution (Enum Singleton)
public enum DatabaseConnection {
INSTANCE;
private final Connection connection;
DatabaseConnection() {
this.connection = createConnection();
}
public Connection getConnection() {
return connection;
}
private Connection createConnection() {
// Connection logic
return DriverManager.getConnection("jdbc:postgresql://localhost/mydb");
}
}
// Usage
Connection conn = DatabaseConnection.INSTANCE.getConnection();
Classic Solution (Double-Checked Locking)
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final Connection connection;
private DatabaseConnection() {
this.connection = createConnection();
}
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
}
"I prefer the enum approach — it's the simplest, thread-safe by default, and handles serialization. Joshua Bloch recommends it in Effective Java."
In Spring Boot, the Singleton Pattern is rarely implemented manually. Spring beans are singleton by default in the IoC container. If the interviewer asks, mention that you use @Service or @Component which are singleton by default.
Exercise 5 — Observer Pattern with Spring Events
"Implement a decoupled notification system."
Solution with Spring Events
// The event
public class OrderCreatedEvent {
private final Order order;
public OrderCreatedEvent(Order order) { this.order = order; }
public Order getOrder() { return order; }
}
// The publisher (Service that creates the order)
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
@Transactional
public Order createOrder(OrderRequest req) {
Order order = orderRepository.save(new Order(req));
eventPublisher.publishEvent(new OrderCreatedEvent(order));
return order;
}
}
// The listeners (react to the event)
@Component
class EmailNotificationListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
emailService.sendConfirmation(event.getOrder());
}
}
@Component
class InventoryListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
inventoryService.decrementStock(event.getOrder().getItems());
}
}
@Component
class AnalyticsListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
analyticsService.trackOrder(event.getOrder());
}
}
Advantages
| Without Observer | With Observer |
|---|---|
| OrderService knows EmailService, InventoryService, AnalyticsService... | OrderService just publishes an event, that's all |
| Adding a listener = modifying OrderService | Adding a listener = creating a class, zero modification |
| Strong coupling | Total decoupling |
"I use Spring Events for the Observer Pattern. The OrderService just publishes an event — it doesn't know who listens. Adding a new reaction is just creating a new @EventListener class."
Summary of Design Patterns
| Pattern | Problem Solved | Spring Example |
|---|---|---|
| Strategy | Vary an algorithm | FeeCalculator with Map |
| Builder | Build complex objects | HttpRequest.Builder |
| Singleton | Single instance | Spring Beans (default) |
| Observer | Decoupled notifications | @EventListener |
| Factory | Create objects without knowing the type | BeanFactory |
| Template Method | Algorithm skeleton | JdbcTemplate, RestTemplate |
| Proxy | Transparent interception | @Transactional, @Cacheable (AOP proxies) |
⬅️ Chapter 3: Algorithms | Chapter 5: Debugging & Code Review →