Skip to content

Commit 23c19a7

Browse files
committed
feat(api): add health check and error handling controllers
1 parent 47fea8a commit 23c19a7

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.example.demo.controller;
2+
3+
import com.example.demo.dto.ApiResponse;
4+
import org.springframework.boot.web.error.ErrorAttributeOptions;
5+
import org.springframework.boot.web.servlet.error.ErrorAttributes;
6+
import org.springframework.boot.web.servlet.error.ErrorController;
7+
import org.springframework.http.HttpStatus;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
import org.springframework.web.context.request.ServletWebRequest;
12+
import org.springframework.web.context.request.WebRequest;
13+
14+
import jakarta.servlet.http.HttpServletRequest;
15+
import java.time.LocalDateTime;
16+
import java.util.Map;
17+
18+
@RestController
19+
public class CustomErrorController implements ErrorController {
20+
21+
private final ErrorAttributes errorAttributes;
22+
23+
public CustomErrorController(ErrorAttributes errorAttributes) {
24+
this.errorAttributes = errorAttributes;
25+
}
26+
27+
@RequestMapping("/error")
28+
public ResponseEntity<ApiResponse<Map<String, Object>>> handleError(HttpServletRequest request) {
29+
WebRequest webRequest = new ServletWebRequest(request);
30+
31+
// Get error attributes with stack trace for debugging (you might want to
32+
// disable this in production)
33+
ErrorAttributeOptions options = ErrorAttributeOptions.of(
34+
ErrorAttributeOptions.Include.MESSAGE,
35+
ErrorAttributeOptions.Include.BINDING_ERRORS,
36+
ErrorAttributeOptions.Include.EXCEPTION);
37+
38+
Map<String, Object> errorAttributes = this.errorAttributes.getErrorAttributes(webRequest, options);
39+
40+
Integer status = (Integer) errorAttributes.get("status");
41+
String message = (String) errorAttributes.get("message");
42+
String error = (String) errorAttributes.get("error");
43+
44+
// Create a more user-friendly message based on status code
45+
String userMessage = getUserFriendlyMessage(status, message, error);
46+
47+
// Create response without data field for cleaner error responses
48+
ApiResponse<Map<String, Object>> response = ApiResponse.<Map<String, Object>>builder()
49+
.message(userMessage)
50+
.timestamp(LocalDateTime.now())
51+
.status(status != null ? status : 500)
52+
.success(false)
53+
.build();
54+
55+
HttpStatus httpStatus = HttpStatus.valueOf(status != null ? status : 500);
56+
return ResponseEntity.status(httpStatus).body(response);
57+
}
58+
59+
private String getUserFriendlyMessage(Integer status, String message, String error) {
60+
if (status == null)
61+
return "An unknown error occurred";
62+
63+
return switch (status) {
64+
case 400 -> "Bad Request: The request was invalid or malformed";
65+
case 401 -> "Unauthorized: Authentication is required to access this resource";
66+
case 403 -> "Forbidden: You don't have permission to access this resource";
67+
case 404 -> "Not Found: The requested resource could not be found";
68+
case 405 -> "Method Not Allowed: The HTTP method is not supported for this endpoint";
69+
case 408 -> "Request Timeout: The request took too long to process";
70+
case 409 -> "Conflict: The request conflicts with the current state of the resource";
71+
case 429 -> "Too Many Requests: Rate limit exceeded, please try again later";
72+
case 500 -> "Internal Server Error: Something went wrong on our end";
73+
case 502 -> "Bad Gateway: Invalid response from upstream server";
74+
case 503 -> "Service Unavailable: The service is temporarily unavailable";
75+
case 504 -> "Gateway Timeout: The upstream server took too long to respond";
76+
default -> error != null ? error : "An error occurred while processing your request";
77+
};
78+
}
79+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.example.demo.controller;
2+
3+
import com.example.demo.dto.ApiResponse;
4+
import com.example.demo.dto.HealthResponse;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.*;
9+
10+
import java.lang.management.ManagementFactory;
11+
import java.time.LocalDateTime;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
15+
@RestController
16+
@RequestMapping("/health")
17+
public class SimpleHealthController {
18+
19+
@Value("${spring.application.name:demo}")
20+
private String applicationName;
21+
22+
@Value("${app.version:1.0.0}")
23+
private String applicationVersion;
24+
25+
@GetMapping
26+
public ResponseEntity<ApiResponse<HealthResponse>> getHealth() {
27+
try {
28+
Map<String, Object> details = new HashMap<>();
29+
details.put("diskSpace", "available");
30+
details.put("memory", "available");
31+
details.put("uptime", ManagementFactory.getRuntimeMXBean().getUptime());
32+
33+
HealthResponse healthResponse = HealthResponse.builder()
34+
.status("UP")
35+
.message("Application health check completed successfully")
36+
.version(applicationVersion)
37+
.timestamp(LocalDateTime.now())
38+
.uptime(ManagementFactory.getRuntimeMXBean().getUptime())
39+
.details(details)
40+
.build();
41+
42+
ApiResponse<HealthResponse> response = ApiResponse.<HealthResponse>builder()
43+
.success(true)
44+
.message("Health check successful")
45+
.status(200)
46+
.timestamp(LocalDateTime.now())
47+
.data(healthResponse)
48+
.build();
49+
50+
return ResponseEntity.ok(response);
51+
52+
} catch (Exception e) {
53+
Map<String, Object> errorDetails = new HashMap<>();
54+
errorDetails.put("error", e.getMessage());
55+
56+
HealthResponse healthResponse = HealthResponse.builder()
57+
.status("DOWN")
58+
.message("Health check failed")
59+
.version(applicationVersion)
60+
.timestamp(LocalDateTime.now())
61+
.details(errorDetails)
62+
.build();
63+
64+
ApiResponse<HealthResponse> response = ApiResponse.<HealthResponse>builder()
65+
.success(false)
66+
.message("Health check failed")
67+
.status(503)
68+
.timestamp(LocalDateTime.now())
69+
.data(healthResponse)
70+
.build();
71+
72+
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)