π‘ Step 2: URI Construction & API Response Patterns
π§ Series Navigation:
- βΆοΈ Backend Development Fundamentals Hub
- βΆοΈ Step 1: RESTful API Design & Best Practices
- β Step 2: URI Construction & API Response Patterns (Current)
- βΆοΈ Step 3: Maven Dependency Management
- βΆοΈ Step 4: Eureka Service Registration & Discovery
- βΆοΈ Step 5: Testing Tools & Authentication Setup
π₯ 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:
ServletUriComponentsBuilder.fromCurrentContextPath()
: Retrieves the base URL of the application dynamically.path("/users/{id}")
: Appends/users/{id}
to the base path.buildAndExpand(response.getUserId())
: Replaces{id}
with the actual user’s ID.toUriString()
: Converts the URI into a stringURI.create(...)
: Converts the string into aURI
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)
)
- Sends the response body (result of
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
- Dynamic URI Construction: Always use dynamic URI building to support multiple deployment environments
- Proper ResponseEntity Usage: Leverage ResponseEntity for complete control over HTTP responses including status, headers, and body
- Comprehensive API Documentation: Use @ApiResponse annotations to define clear API contracts with examples
- RESTful Resource Pointing: Location headers should point to created resources, not the endpoints that created them
- 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
π§ Next Steps in Backend Development Fundamentals:
- βΆοΈ Backend Development Fundamentals Hub
- βΆοΈ Step 1: RESTful API Design & Best Practices
- β Step 2: URI Construction & API Response Patterns (Current)
- βΆοΈ Step 3: Maven Dependency Management
- βΆοΈ Step 4: Eureka Service Registration & Discovery
- βΆοΈ Step 5: Testing Tools & Authentication Setup
Part of the AdventureTube technical blog series supporting the JavaiOS YouTube channel.