REST API Design Interview Questions

What is the difference between @Controller and @RestController?

@Controller returns view names (Thymeleaf/JSP) unless a method has @ResponseBody. @RestController = @Controller + @ResponseBody on every method: return values are written to the response body as JSON by Jackson's HttpMessageConverter.

@PathVariable vs @RequestParam vs @RequestBody?

AnnotationReads fromExample
@PathVariableURL path segment, identifies a resourceGET /api/products/42
@RequestParamquery string, filter/sort/pageGET /api/products?category=mobiles&page=0
@RequestBodyJSON body, mapped to an objectPOST /api/products with {"name":"..."}

How do you return proper HTTP status codes?

Return ResponseEntity (full control of status, headers, body) or put @ResponseStatus on the method or on an exception class.

@PostMapping
public ResponseEntity<ProductResponse> create(@Valid @RequestBody CreateProductRequest req) {
    ProductResponse saved = productService.create(req);
    URI location = URI.create("/api/products/" + saved.id());
    return ResponseEntity.created(location).body(saved);   // 201 + Location header
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)                       // 204
public void delete(@PathVariable Long id) { productService.delete(id); }

Which status codes should a REST API use?

CodeWhen
200 OKsuccessful GET / PUT / PATCH with body
201 CreatedPOST created a resource (add Location)
204 No Contentsuccessful DELETE or update with no body
400 Bad Requestvalidation failed, malformed JSON
401 / 403not authenticated / authenticated but not allowed
404 Not Foundresource does not exist
409 Conflictduplicate email, version conflict, insufficient stock
500unexpected server error (never leak stack traces)

PUT vs PATCH vs POST, and what is idempotency?

An operation is idempotent if repeating it gives the same result. GET, PUT and DELETE are idempotent; POST is not (two calls create two orders). PUT replaces the whole resource; PATCH updates only the fields sent; POST creates or triggers an action. For payment/order POSTs, accept an Idempotency-Key header so client retries do not charge twice (see the Coding Round).

How do you handle exceptions globally in Spring Boot?

Use @RestControllerAdvice with @ExceptionHandler methods. Spring Boot 3 supports RFC 7807 ProblemDetail for a standard error format.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ProblemDetail handleNotFound(ProductNotFoundException ex) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
        pd.setTitle("Product not found");
        return pd;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
        ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        pd.setTitle("Validation failed");
        Map<String, String> errors = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors()
          .forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
        pd.setProperty("errors", errors);
        return pd;
    }
}

Example

curl -i localhost:8080/api/products/999

Example output

HTTP/1.1 404
Content-Type: application/problem+json

{"type":"about:blank","title":"Product not found","status":404,"detail":"Product 999 not found","instance":"/api/products/999"}

How does request validation work?

Add spring-boot-starter-validation, put jakarta.validation.constraints annotations on the DTO and @Valid on the parameter. Invalid input throws MethodArgumentNotValidException (400). Use @Validated on the class to validate @PathVariable/@RequestParam too.

public record CreateProductRequest(
        @NotBlank String name,
        @Size(max = 500) String description,
        @NotNull @Positive BigDecimal price,
        @PositiveOrZero int stock,
        @NotNull Long categoryId) { }

Why use DTOs instead of returning entities?

  • Entities can leak internal or sensitive fields (password hash, audit columns).
  • Lazy JPA relations cause LazyInitializationException or infinite JSON recursion.
  • The API contract stays stable when the database schema changes.
  • Request DTOs carry validation rules for that operation only (create vs update).

Java records make concise DTOs; map with a small method or MapStruct.

How do you implement pagination and sorting?

@GetMapping
public Page<ProductResponse> list(@RequestParam(required = false) String category,
                                  @PageableDefault(size = 20, sort = "price") Pageable pageable) {
    return productService.search(category, pageable);
}
// GET /api/products?category=mobiles&page=1&size=10&sort=price,desc

Never return an unbounded list from a large table. For infinite scroll on big tables prefer keyset (cursor) pagination: WHERE id > :lastId ORDER BY id LIMIT 20.

How do you version a REST API?

ApproachExample
URI (most common in India projects)/api/v1/products
HeaderX-API-Version: 2
Media typeAccept: application/vnd.shopease.v2+json
Query param?version=2

Keep old versions working until clients migrate; add fields in a backward-compatible way instead of creating a new version for every change.

How do you enable CORS for a React frontend?

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:5173", "https://shopease.in")
                .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE")
                .allowCredentials(true);
    }
}

For one controller, @CrossOrigin(origins = "...") is enough. With Spring Security, also call http.cors(Customizer.withDefaults()).