Step 2: URI Construction & API Response Patterns

πŸŽ₯ YouTube Video: Coming Soon – Subscribe to JavaiOS Channel


URI Construction and API Response Annotations in AuthController πŸ“‘

This document analyzes the register method in AuthController, focusing on the construction of the URI and the @ApiResponse annotations that define the API response structure. Learn professional patterns for building robust, maintainable API endpoints.


🎯 Overview

Professional API development requires careful attention to URI construction, response handling, and documentation. This guide examines real-world patterns from the AdventureTube authentication service, showing how to build scalable, environment-agnostic API endpoints.


πŸ”§ Complete Implementation Example

Let’s examine the complete register method implementation:

@Operation(summary = "Signup user") @ApiResponse(responseCode = "201") // Created @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(implementation = RestAPIResponse.class))) // Unauthorized error @ApiResponse(responseCode = "409", content = @Content(schema = @Schema(implementation = RestAPIResponse.class))) // Conflict error @ApiResponse(responseCode = "500", content = @Content(schema = @Schema(implementation = RestAPIResponse.class))) // Internal server error @PostMapping(value = "/register") public ResponseEntity register(@Valid @RequestBody MemberRegisterRequest request) {     MemberRegisterResponse response = authService.register(request); // Register user and get response          URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath()                       .path("/users/{id}")                       .buildAndExpand(response.getUserId()) // Assuming getUserId() returns the new user's ID                       .toUriString());                            return ResponseEntity.created(uri).body(response); } 

πŸ—οΈ URI Construction Deep Dive

Understanding Context Path

The context path is the base URL under which the application is running. It is dynamically retrieved to ensure flexibility across different environments.

Breakdown of URI Construction

URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath()                       .path("/users/{id}")                       .buildAndExpand(response.getUserId())                       .toUriString()); 

Step-by-Step Process:

  1. ServletUriComponentsBuilder.fromCurrentContextPath(): Retrieves the base URL of the application dynamically
  2. .path("/users/{id}"): Appends /users/{id} to the base path
  3. .buildAndExpand(response.getUserId()): Replaces {id} with the actual user’s ID
  4. .toUriString(): Converts the URI into a string
  5. URI.create(...): Converts the string into a URI object

Environment-Specific URI Generation

Deployment Context Generated URI
localhost:8080 http://localhost:8080/users/123
localhost:8080/myapp http://localhost:8080/myapp/users/123
api.adventuretube.net https://api.adventuretube.net/users/123
staging-api.adventuretube.net https://staging-api.adventuretube.net/users/123

Benefits of Dynamic URI Construction

  • βœ… Ensures dynamic URI resolution, making the code adaptable across environments
  • βœ… Prevents hardcoding of base URLs, improving maintainability
  • βœ… Follows RESTful principles by pointing to the created user instead of the registration endpoint
  • βœ… Supports multiple deployment scenarios without code changes

πŸ“‹ Advanced URI Construction Patterns

1. Simple Resource Creation

// For creating a new user URI userUri = ServletUriComponentsBuilder.fromCurrentContextPath()     .path("/users/{id}")     .buildAndExpand(userId)     .toUri(); 

2. Nested Resource Creation

// For creating a user's order URI orderUri = ServletUriComponentsBuilder.fromCurrentContextPath()     .path("/users/{userId}/orders/{orderId}")     .buildAndExpand(userId, orderId)     .toUri(); 

3. Gateway-Aware URI Construction

// For microservice environments with API Gateway @Value("${app.gateway.base-url}") private String gatewayBaseUrl;  public URI createResourceUri(String resourcePath, Object... pathVariables) {     if (gatewayBaseUrl != null && !gatewayBaseUrl.isEmpty()) {         return UriComponentsBuilder.fromHttpUrl(gatewayBaseUrl)             .path(resourcePath)             .buildAndExpand(pathVariables)             .toUri();     } else {         return ServletUriComponentsBuilder.fromCurrentContextPath()             .path(resourcePath)             .buildAndExpand(pathVariables)             .toUri();     } } 

4. Query Parameter Handling

// For URIs with query parameters URI searchUri = ServletUriComponentsBuilder.fromCurrentContextPath()     .path("/users")     .queryParam("page", page)     .queryParam("size", size)     .queryParam("sort", sortBy)     .build()     .toUri(); 

🎭 ResponseEntity Analysis

Understanding ResponseEntity<T>

ResponseEntity is a wrapper that provides complete control over the HTTP response:

  • βœ… HTTP status codes
  • βœ… Headers
  • βœ… Response body

Breakdown of ResponseEntity Usage

return ResponseEntity.created(uri).body(response); 

Components:

  • ResponseEntity.created(uri):
    • Returns 201 Created HTTP status
    • Includes the Location header with the newly created resource URI
  • .body(response):
    • Sends the response body (result of register(request))

Common ResponseEntity Patterns

HTTP Status Example Code Use Case
200 OK ResponseEntity.ok().body(data) Successful data retrieval
201 Created ResponseEntity.created(uri).body(data) Resource successfully created
204 No Content ResponseEntity.noContent().build() Successful operation with no response body
400 Bad Request ResponseEntity.badRequest().body("Invalid input") Client sent invalid data
404 Not Found ResponseEntity.notFound().build() Requested resource doesn’t exist
409 Conflict ResponseEntity.status(HttpStatus.CONFLICT).body(error) Resource already exists

πŸ“š API Response Annotations Analysis

Understanding @ApiResponse

These annotations define the expected API responses in OpenAPI (Swagger) documentation, providing clear contract definitions for API consumers.

Response Code Description Schema
201 Created User successfully registered MemberRegisterResponse
401 Unauthorized Authentication failure (e.g., invalid token) RestAPIResponse
409 Conflict User already exists or business rule conflicts RestAPIResponse
500 Internal Server Error Unexpected issue on the server RestAPIResponse

Complete ApiResponse Configuration

@Operation(summary = "Register new user", description = "Creates a new user account with provided details") @ApiResponses({     @ApiResponse(         responseCode = "201",         description = "User successfully created",         content = @Content(             mediaType = "application/json",             schema = @Schema(implementation = MemberRegisterResponse.class),             examples = @ExampleObject(                 name = "Successful Registration",                 value = """                 {                   "userId": "8311471e-3845-49e2-90b9-826f338efc74",                   "email": "strider@adventuretube.net",                   "username": "striderlee",                   "role": "USER",                   "accessToken": "eyJhbGciOiJIUzUx...",                   "refreshToken": "def5678uvw...",                   "expiresIn": 3600                 }                 """             )         )     ),     @ApiResponse(         responseCode = "409",          description = "User already exists",         content = @Content(             mediaType = "application/json",             schema = @Schema(implementation = RestAPIResponse.class),             examples = @ExampleObject(                 name = "Conflict Error",                 value = """                 {                   "status": "error",                   "message": "User with this email already exists",                   "timestamp": "2025-07-16T10:30:00Z"                 }                 """             )         )     ) }) @PostMapping("/register") public ResponseEntity register(@Valid @RequestBody MemberRegisterRequest request) {     // Implementation } 

πŸ”’ Security and Validation Patterns

Input Validation

@PostMapping("/register") public ResponseEntity register(         @Valid @RequestBody MemberRegisterRequest request,         BindingResult bindingResult) {          // Check for validation errors     if (bindingResult.hasErrors()) {         List errors = bindingResult.getFieldErrors()             .stream()             .map(error -> error.getField() + ": " + error.getDefaultMessage())             .collect(Collectors.toList());                      return ResponseEntity.badRequest()             .body(new ErrorResponse("Validation failed", errors));     }          // Process registration     MemberRegisterResponse response = authService.register(request);          URI uri = createResourceUri("/users/{id}", response.getUserId());     return ResponseEntity.created(uri).body(response); } 

Error Response Standardization

@Data @Builder public class RestAPIResponse {     private String status;     private String message;     private String timestamp;     private Map details;          public static RestAPIResponse error(String message) {         return RestAPIResponse.builder()             .status("error")             .message(message)             .timestamp(Instant.now().toString())             .build();     }          public static RestAPIResponse success(String message) {         return RestAPIResponse.builder()             .status("success")             .message(message)             .timestamp(Instant.now().toString())             .build();     } } 

πŸš€ Production-Ready Implementation

Complete AuthController Method

@RestController @RequestMapping("/auth") @Validated @Slf4j public class AuthController {          private final AuthService authService;     private final UriConstructionService uriService;          @Operation(summary = "Register new user")     @ApiResponses({         @ApiResponse(responseCode = "201", description = "User created successfully"),         @ApiResponse(responseCode = "400", description = "Invalid input data"),         @ApiResponse(responseCode = "409", description = "User already exists"),         @ApiResponse(responseCode = "500", description = "Internal server error")     })     @PostMapping("/register")     public ResponseEntity register(             @Valid @RequestBody MemberRegisterRequest request) {                  try {             log.info("User registration request received for email: {}", request.getEmail());                          MemberRegisterResponse response = authService.register(request);                          URI locationUri = uriService.createResourceUri("/users/{id}", response.getUserId());                          log.info("User registered successfully with ID: {}", response.getUserId());                          return ResponseEntity.created(locationUri).body(response);                      } catch (UserAlreadyExistsException e) {             log.warn("Registration failed - user already exists: {}", request.getEmail());             return ResponseEntity.status(HttpStatus.CONFLICT)                 .body(RestAPIResponse.error("User with this email already exists"));                          } catch (Exception e) {             log.error("Registration failed for user: {}", request.getEmail(), e);             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)                 .body(RestAPIResponse.error("Registration failed due to internal error"));         }     } } 

URI Construction Service

@Service @Component public class UriConstructionService {          @Value("${app.gateway.base-url:}")     private String gatewayBaseUrl;          public URI createResourceUri(String resourcePath, Object... pathVariables) {         if (StringUtils.hasText(gatewayBaseUrl)) {             return UriComponentsBuilder.fromHttpUrl(gatewayBaseUrl)                 .path(resourcePath)                 .buildAndExpand(pathVariables)                 .toUri();         }                  return ServletUriComponentsBuilder.fromCurrentContextPath()             .path(resourcePath)             .buildAndExpand(pathVariables)             .toUri();     }          public URI createCollectionUri(String collectionPath, Map queryParams) {         UriComponentsBuilder builder = StringUtils.hasText(gatewayBaseUrl)             ? UriComponentsBuilder.fromHttpUrl(gatewayBaseUrl)             : ServletUriComponentsBuilder.fromCurrentContextPath();                      builder.path(collectionPath);                  queryParams.forEach(builder::queryParam);                  return builder.build().toUri();     } } 

βœ… Best Practices Checklist

URI Construction βœ…

  • βœ… Use dynamic URI construction for environment flexibility
  • βœ… Point to created resources, not endpoints
  • βœ… Handle microservice vs gateway URL scenarios
  • βœ… Validate URI parameters before construction

Response Handling βœ…

  • βœ… Use appropriate HTTP status codes
  • βœ… Include Location header for created resources
  • βœ… Provide consistent error response format
  • βœ… Handle validation errors gracefully

API Documentation βœ…

  • βœ… Document all possible response codes
  • βœ… Include response schemas and examples
  • βœ… Provide clear operation summaries
  • βœ… Document error response formats

Security & Validation βœ…

  • βœ… Validate all input data
  • βœ… Log security-relevant events
  • βœ… Handle exceptions appropriately
  • βœ… Use consistent error messaging

πŸŽ“ Key Takeaways

  1. Dynamic URI Construction: Always use dynamic URI building to support multiple deployment environments
  2. Proper ResponseEntity Usage: Leverage ResponseEntity for complete control over HTTP responses including status, headers, and body
  3. Comprehensive API Documentation: Use @ApiResponse annotations to define clear API contracts with examples
  4. RESTful Resource Pointing: Location headers should point to created resources, not the endpoints that created them
  5. Environment Flexibility: Design URI construction to work across local development, staging, and production environments

This structured approach enhances maintainability, clarity, and robustness of your API endpoints while supporting scalable microservice architectures.


πŸš€ Continue Your Backend Development Journey

Part of the AdventureTube technical blog series supporting the JavaiOS YouTube channel.

Leave a Comment

Your email address will not be published. Required fields are marked *