Skip to main content

Chapter 4: REST Design & Design Patterns

Objective

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 PracticeWhy
DTOs instead of entitiesNever expose JPA entities in the API
@ValidValidate server-side inputs
201 Created for POSTCorrect HTTP codes
404 Not Found if absentReturn the correct status
Pagination on listsNever return all data at once
Java Records for DTOsImmutable, concise, auto-generate equals/hashCode/toString
Interview Phrase

"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​

RuleExample
Plural names/products not /product
HTTP verbs = actionsGET = 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 securityADMIN 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​

TechniqueWhy
final classPrevents inheritance
final fieldsImmutability
private constructorEnforce Builder usage
Map.copyOf()Defensive copy — object cannot be modified after creation
Objects.requireNonNull()Validate required fields
Fluent API (return this)Method chaining
Interview Phrase

"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;
}
}
Interview Phrase

"I prefer the enum approach — it's the simplest, thread-safe by default, and handles serialization. Joshua Bloch recommends it in Effective Java."

Note Spring Boot

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 ObserverWith Observer
OrderService knows EmailService, InventoryService, AnalyticsService...OrderService just publishes an event, that's all
Adding a listener = modifying OrderServiceAdding a listener = creating a class, zero modification
Strong couplingTotal decoupling
Interview Phrase

"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​

PatternProblem SolvedSpring Example
StrategyVary an algorithmFeeCalculator with Map
BuilderBuild complex objectsHttpRequest.Builder
SingletonSingle instanceSpring Beans (default)
ObserverDecoupled notifications@EventListener
FactoryCreate objects without knowing the typeBeanFactory
Template MethodAlgorithm skeletonJdbcTemplate, RestTemplate
ProxyTransparent interception@Transactional, @Cacheable (AOP proxies)

⬅️ Chapter 3: Algorithms | Chapter 5: Debugging & Code Review →