Java Notes
Building RESTful APIs with Spring Boot - REST principles, controller annotations, request/response handling, pagination, HATEOAS, and API versioning.
REST Principles
REST (Representational State Transfer) is an architectural style for designing networked applications:
| Principle | Description |
|---|---|
| Stateless | Each request contains all info needed |
| Client-Server | Separation of concerns |
| Uniform Interface | Standard HTTP methods (GET, POST, PUT, DELETE) |
| Resource-Based | URLs represent resources, not actions |
| Layered System | Client doesn't know if connected directly to server |
REST URL Design
| GET /api/users | Get all users |
| GET /api/users/1 | Get user with id 1 |
| POST /api/users | Create new user |
| PUT /api/users/1 | Update user 1 |
| DELETE /api/users/1 | Delete user 1 |
| GET /api/users/1/orders | Get orders of user 1 |
| GET /api/users?page=1&size=10 | Paginated users |
Request and Response Handling
Path Variables
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable Long orderId) {
return orderService.findByUserAndId(userId, orderId);
}Request Parameters
@GetMapping("/products")
public Page<Product> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "asc") String direction,
@RequestParam(required = false) String category) {
Sort sort = direction.equalsIgnoreCase("desc")
? Sort.by(sortBy).descending()
: Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(page, size, sort);
if (category != null) {
return productService.findByCategory(category, pageable);
}
return productService.findAll(pageable);
}Request Body
@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(@RequestBody @Valid UserRequest request) {
User user = userService.create(request);
UserResponse response = mapToResponse(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(user.getId())
.toUri();
return ResponseEntity.created(location).body(response);
}Request Headers
@GetMapping("/profile")
public UserProfile getProfile(
@RequestHeader("Authorization") String authHeader,
@RequestHeader(value = "Accept-Language", defaultValue = "en") String language) {
String token = authHeader.replace("Bearer ", "");
return userService.getProfile(token, language);
}ResponseEntity for Full Control
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody @Valid ProductRequest request) {
Product product = productService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.header("X-Product-Id", product.getId().toString())
.body(product);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
}DTO Pattern (Data Transfer Objects)
// Request DTO
@Data
public class CreateUserRequest {
@NotBlank(message = "Name is required")
private String name;
@Email(message = "Invalid email")
@NotBlank
private String email;
@Min(18) @Max(120)
private int age;
@NotBlank
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
}
// Response DTO (no password!)
@Data
@Builder
public class UserResponse {
private Long id;
private String name;
private String email;
private int age;
private LocalDateTime createdAt;
}
// Mapper
@Component
public class UserMapper {
public User toEntity(CreateUserRequest request) {
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setAge(request.getAge());
return user;
}
public UserResponse toResponse(User user) {
return UserResponse.builder()
.id(user.getId())
.name(user.getName())
.email(user.getEmail())
.age(user.getAge())
.createdAt(user.getCreatedAt())
.build();
}
}Pagination
@GetMapping
public ResponseEntity<Map<String, Object>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Page<User> userPage = userService.findAll(PageRequest.of(page, size));
Map<String, Object> response = new HashMap<>();
response.put("users", userPage.getContent());
response.put("currentPage", userPage.getNumber());
response.put("totalItems", userPage.getTotalElements());
response.put("totalPages", userPage.getTotalPages());
response.put("hasNext", userPage.hasNext());
response.put("hasPrevious", userPage.hasPrevious());
return ResponseEntity.ok(response);
}API Versioning Strategies
// 1. URL Versioning (Most Common)
@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 { }
@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 { }
// 2. Header Versioning
@GetMapping(value = "/users", headers = "X-API-VERSION=1")
public List<UserV1> getUsersV1() { }
@GetMapping(value = "/users", headers = "X-API-VERSION=2")
public List<UserV2> getUsersV2() { }
// 3. Content Negotiation
@GetMapping(value = "/users", produces = "application/vnd.company.v1+json")
public List<UserV1> getUsersV1() { }CORS Configuration
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "https://myapp.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}Interview Key Points
@RestController=@Controller+@ResponseBodyon every method- Use
ResponseEntityfor full control over status codes and headers - DTOs separate internal model from API contract
@PathVariablefor URL segments;@RequestParamfor query params- Always use
@Validwith request DTOs for input validation - Use pagination for large datasets (never return all records)
- Version APIs via URL path (
/api/v1/,/api/v2/) - CORS must be configured for frontend-backend separation
Summary
In this chapter, we learned about REST API with Spring Boot in detail. Here are the key points:
- Basic introduction to the concept and why it is needed
- Syntax and structure with complete examples
- Internal working and best practices
- Real-world applications and use cases
- Common mistakes and how to avoid them
- How this topic is asked in interviews
To practice these concepts, write and run the code in your IDE and verify the output. Modify each example and experiment so that you deeply understand the concept.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for REST API with Spring Boot.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, spring, framework, boot
Related Java Master Course Topics