Skip to main content

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 patternSuccess returnFailure return
create(req) / update(id, req) / get(id)*ResponseV5 instancenull
list() familyList<*ResponseV5>empty list (never null)
delete(id)truefalse
exists(id)true (present)false (absent or error)
count() familylong (≥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/_message for 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.

CodeMeaningTypical cause
E1001INVALID_INPUTMissing required field, type mismatch
E1002VALIDATION_FAILEDID prefix violation (SITE_, etc.)
E1100UNAUTHORIZEDMissing or invalid api_key
E1101FORBIDDENInsufficient permissions
E1200CONFLICTDuplicate ID, FK conflict
E1300NOT_FOUNDNo such ID
E1400DOMAIN_RULE_VIOLATIONOrder state transition not allowed, etc.
E1500DEPENDENCY_FAILEDExternal system failed to respond
E1900INTERNAL_ERRORInternal 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:

  1. No data on the server (NOT_FOUND, E1300)
  2. 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