3. Response Format and Error Handling
All calls to the V5 API return a Type DTO or boolean/long. In the event of a failure, the following safe default values are returned.
3.1 V5 Response Patterns
| Method Pattern | Success Return | Failure Return |
|---|---|---|
create(req) / update(id, req) / get(id) | *ResponseV5 Instance | null |
list() Series | List<*ResponseV5> | Empty List (Never null) |
delete(id) | true | false |
exists(id) | true (Exists) | false (Non-existent or Error) |
count() Series | long (≥0) | 0 |
Order Lifecycle (start/end/pause/resume/abort) | true (Success) | false (Transition Impossible, etc.) |
Exceptions are not thrown. HTTP errors, server ERRORs, and JSON parsing failures are all absorbed into the safe default values shown in the table above. Error causes can be verified in the debug logs or the envelope's
_code/_message.
3.2 Internal Wire Format (Reference)
The V5 server responds in the following envelope format. Since the client library parses this automatically, general users do not need to concern themselves with it.
// 단건 성공
{ "_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 sends an ERROR response, the following codes are populated in the _code field. The authoritative source is the product's plantpulse.core.exception.ErrorCode, and the HTTP status is also determined by that enumeration — since the code and status are paired, you may branch based on either one.
| Code | HTTP | Name | Common Cause |
|---|---|---|---|
E1000 | 400 | INVALID_REQUEST | Syntax error, type mismatch |
E1001 | 400 | MISSING_PARAMETER | Required parameter missing |
E1002 | 422 | VALIDATION_FAILED | Value range, format, or domain rule violation (e.g., ID prefix) |
E1100 | 401 | UNAUTHORIZED | api_key missing or invalid |
E1101 | 429 | AUTH_TOO_MANY_ATTEMPTS | Accumulated authentication failures from the same IP — see Retry-After |
E1102 | 401 | TOKEN_EXPIRED | Token expired — recoverable via re-issuance |
E1103 | 401 | TOKEN_REVOKED | Token revoked — the same token cannot be recovered |
E1104 | 401 | TOKEN_IP_MISMATCH | IP bound to token differs from calling IP |
E1200 | 403 | FORBIDDEN | Authenticated but lacking permissions |
E1300 | 404 | NOT_FOUND | Corresponding ID not found |
E1400 | 405 | METHOD_NOT_ALLOWED | Method not allowed |
E1500 | 409 | CONFLICT | Resource conflict |
E1501 | 409 | DUPLICATE_ENTITY | Entity already exists |
E1600 | 413 | PAYLOAD_TOO_LARGE | Request body too large |
E1700 | 415 | UNSUPPORTED_MEDIA_TYPE | Unsupported media type |
E1800 | 422 | IDEMPOTENCY_CONFLICT | Same Idempotency-Key + different body |
E1801 | 409 | IDEMPOTENCY_IN_PROGRESS | Same key is being processed — retry after a short delay |
E1900 | 429 | RATE_LIMIT_EXCEEDED | Rate limit exceeded |
E5000 | 500 | INTERNAL_ERROR | Server internal error |
E5100 | 500 | SERVICE_ERROR | Service processing error |
E5200 | 500 | DAO_ERROR | Data access error |
E5300 | 500 | ENGINE_ERROR | Engine processing error |
E5400 | 502 | EXTERNAL_SERVICE_ERROR | External system call failure |
E5500 | 503 | SERVICE_UNAVAILABLE | Service unavailable — this is a "failure to execute," not a "determination." See Retry-After |
E5503 | 503 | SHUTTING_DOWN | Server shutting down — hand over to another node or wait for Retry-After |
E5600 | 504 | TIMEOUT | Server-side request timeout |
E1900 (Rate Limit) is resolved by slowing down. E1101 (Too Many Auth Attempts) is not resolved by slowing down — the credentials themselves are incorrect, and that IP is locked until the window closes. If both are grouped under the same backoff, the latter will retry indefinitely.
400 means the request cannot be «interpreted», while 422 means it was interpreted but the «content» does not meet the rules (as of 2026-08-31). The status code distinguishes whether the format needs to be fixed or the value needs to be changed — humans read the message, but agents determine the next attempt based on the status code.
Common Rejections
| HTTP | reason | Meaning |
|---|---|---|
| 422 | asset_statement.eql.missing_columns | The statement EQL does not provide a required field for this type — Equipment Statement |
| 422 | asset_statement.eql.unknown_type | Non-existent statement type |
| 422 | alarm.invalid.alarm_config_id | A manually assigned ID was provided for alarm setting creation. Receive via GET /alarm/config/new-id |
| 409 | — | Attempted to create a second band alarm setting for the same tag. Only one per tag |
3.4 Extracting Error Information
V5 service methods do not expose the envelope directly, so error details are verified using the following two methods.
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 helper in BaseServiceV5 extracts error information from JSONObject responses. This is useful when creating a custom service that captures the envelope by extending the library.
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 → Retrieval
if (client.site().exists("SITE_DJ")) {
SiteResponseV5 site = client.site().get("SITE_DJ");
// ...
}
Order Lifecycle (Boolean Return)
String orderId = "ORDER_000001";
if (!client.order().start(orderId)) {
// 전이 불가 (예: 이미 START 상태)
log.warn("작업 시작 실패 — 현재 상태 확인");
return;
}
// 작업 진행 ...
if (!client.order().end(orderId)) {
log.warn("작업 종료 실패");
}
3.6 Retry Patterns
V5 does not perform automatic retries. If retries are needed for transient network errors, you must implement them directly.
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);
503 · 429 and Retry-After
When the server cannot accept requests due to restarting or overload, Retry-After (integer in seconds) is provided. Java clients adhere to this header but will not wait longer than 30 seconds by default. When calling directly, follow these guidelines:
| Scenario | Response |
|---|---|
| Web layer is down (Restart · Redeployment) — Proxy responds | 503 E5500, Retry-After: 15 |
| Server shutdown in progress | 503 E5503, Retry-After: 15 |
| Backend storage temporarily unavailable (DB restart, etc.) | 503 E5500, Retry-After: 15 |
| Per-token request limit exceeded | 429 E1900, Retry-After calculates the limit |
| Stream concurrency limit exceeded | 429 E1900 or 503 E5500, Retry-After |
- For Reads, wait for the specified duration and call again.
- For Writes, call again with the same
Idempotency-Key. A request interrupted by a restart may have already been applied. - 500 indicates a failure, and 502
E5400indicates an external call failure. Neither providesRetry-After.
3.7 Distinguishing Empty Responses and Null Responses
In V5, null or false can imply both of the following:
- No data on server (
NOT_FOUND,E1300) - Call failed (Network error, validation failure, etc.)
To distinguish between these, use exists().
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 Check
- Site Service — Master Data Starting Point