3. Response Format and Error Handling
Every V5 API call returns either a typed DTO or a boolean/long. On failure, the following safe defaults are returned.
3.1 V5 Response Patterns
| Method pattern | Success return | Failure return |
|---|---|---|
create(req) / update(id, req) / get(id) | *ResponseV5 instance | null |
list() family | List<*ResponseV5> | empty list (never null) |
delete(id) | true | false |
exists(id) | true (present) | false (absent or error) |
count() family | long (≥0) | 0 |
Order lifecycle (start/end/pause/resume/abort) | true (success) | false (transition not allowed, etc.) |
No exceptions are thrown. HTTP errors, server ERROR responses, and JSON parsing failures are all absorbed into the safe defaults in the table above. Check the debug log or the envelope's
_code/_messagefor the cause of an error.
3.2 Internal wire format (reference)
The V5 server responds with the envelope shown below. The client library parses it automatically, so you do not need to deal with it in normal use.
// 단건 성공
{ "_status": "OK", "data": { ... } }
// 목록 성공
{ "_status": "OK", "data": [ { ... }, { ... } ] }
// 목록 — items 키 안에 배열
{ "_status": "OK", "data": { "items": [ ... ] } }
// 에러
{
"_status": "ERROR",
"_code": "E1002",
"_message": "validation failed: ...",
"_http_status": 400
}
3.3 V5 ErrorCode Mapping
When the server returns an ERROR response, the _code field contains one of the following codes.
| Code | Meaning | Typical cause |
|---|---|---|
E1001 | INVALID_INPUT | Missing required field, type mismatch |
E1002 | VALIDATION_FAILED | ID prefix violation (SITE_, etc.) |
E1100 | UNAUTHORIZED | Missing or invalid api_key |
E1101 | FORBIDDEN | Insufficient permissions |
E1200 | CONFLICT | Duplicate ID, FK conflict |
E1300 | NOT_FOUND | No such ID |
E1400 | DOMAIN_RULE_VIOLATION | Order state transition not allowed, etc. |
E1500 | DEPENDENCY_FAILED | External system failed to respond |
E1900 | INTERNAL_ERROR | Internal server error |
3.4 Extracting Error Information
V5 service methods do not expose the envelope directly, so error details are obtained in one of two ways.
Method 1 — Debug mode
APIClient_V5 client = new APIClient_V5(proto, host, port, user, token, true);
client.connect();
SiteResponseV5 result = client.site().create(req);
if (result == null) {
// 콘솔 로그에서 envelope 확인 가능
System.err.println("등록 실패 — 디버그 로그 확인");
}
Console output:
[V5] POST /api/v5/site body={...}
[V5] response: 400 {"_status":"ERROR","_code":"E1002","_message":"site_id must start with SITE_"}
Method 2 — Using BaseServiceV5 static methods (custom extension)
The BaseServiceV5 helper extracts error information from a JSONObject response. This is useful when extending the library to build a custom service that captures the envelope.
import plantpulse.api.v5.service.BaseServiceV5;
import plantpulse.json.JSONObject;
// 환경 (envelope) JSONObject 가 있다면
String code = BaseServiceV5.getErrorCode(env); // 예: "E1002"
String message = BaseServiceV5.getErrorMessage(env); // 예: "validation failed: ..."
int httpStatus = BaseServiceV5.getHttpStatus(env); // 예: 400
3.5 Safe Call Patterns
CRUD calls
import plantpulse.api.v5.dto.request.SiteRequestV5;
import plantpulse.api.v5.dto.response.SiteResponseV5;
SiteRequestV5 req = new SiteRequestV5();
req.setSite_id("SITE_DJ");
req.setSite_name("대전공장");
SiteResponseV5 created = client.site().create(req);
if (created == null) {
// 실패 처리 — 예: 알람, 재시도, 다른 경로 시도
log.warn("사이트 생성 실패");
return;
}
System.out.println("등록 완료: " + created.getSite_id());
List calls
List<SiteResponseV5> sites = client.site().list();
// 실패해도 null 이 아님 — 그대로 순회 가능
for (SiteResponseV5 s : sites) {
System.out.println(s.getSite_id());
}
if (sites.isEmpty()) {
log.info("등록된 사이트 없음");
}
Existence check → lookup
if (client.site().exists("SITE_DJ")) {
SiteResponseV5 site = client.site().get("SITE_DJ");
// ...
}
Order lifecycle (boolean return)
String orderId = "ORD_20260514_001";
if (!client.order().start(orderId)) {
// 전이 불가 (예: 이미 START 상태)
log.warn("작업 시작 실패 — 현재 상태 확인");
return;
}
// 작업 진행 ...
if (!client.order().end(orderId)) {
log.warn("작업 종료 실패");
}
3.6 Retry Pattern
V5 does not retry automatically. If you need retries to handle transient network errors, implement them yourself.
import java.util.function.Supplier;
public static <T> T callWithRetry(Supplier<T> apiCall, int maxRetries, long baseDelayMs) {
int attempt = 0;
while (true) {
T result = apiCall.get();
if (result != null) return result; // 성공
if (++attempt >= maxRetries) return null; // 포기
try {
Thread.sleep(baseDelayMs * attempt); // 지수 백오프
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return null;
}
}
}
// 사용
SiteResponseV5 site = callWithRetry(
() -> client.site().get("SITE_DJ"),
3,
1_000L);
3.7 Distinguishing Empty Responses from Null Responses
In V5, null or false covers both of the following cases:
- No data on the server (
NOT_FOUND,E1300) - The call failed (network error, validation failure, etc.)
To tell them apart, use exists() alongside the call.
if (!client.site().exists("SITE_DJ")) {
log.info("사이트가 등록되어 있지 않습니다");
// 신규 등록 로직
} else {
SiteResponseV5 site = client.site().get("SITE_DJ");
if (site == null) {
log.error("사이트는 존재하지만 조회 실패 — 일시적 오류 가능");
}
}
Next Steps
- System service — health checks
- Site service — starting point for master data