Skip to content

Commit 3e9f105

Browse files
Devesh/sk 2814 java public interface cleanup (#315)
* docs: add Java SDK nomenclature cleanup design spec Captures the design for public interface renames per the server-side SDK nomenclature changes spec: credential field fallbacks (clientId/keyId/tokenUri), skyflow_id→skyflowId in Get/Query responses, and QueryResponse errors/tokenizedData field additions. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: clarify tokenizedData reasoning in nomenclature cleanup spec Adds explanation for why tokenizedData change is valid despite the Query API currently not returning tokens — based on V1FieldRecords schema support and cross-SDK consistency requirement. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: expand reasoning in Java nomenclature cleanup spec Adds detailed rationale to each design section: why the naming convention matters, why the fallback strategy was chosen over a hard cut, why skyflow_id normalization is inconsistent today, the tokenizedData API schema vs docs discrepancy, and why getErrors() is missing only from QueryResponse. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: correct tokenizedData implementation rationale in spec Clarifies that ignoring record.getTokens() in getFormattedQueryRecord is intentional (Query API cannot return tokens), and that the fix is to promote the toString() hack into a real always-empty field rather than reading from the API response. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: remove tokenizedData from scope in nomenclature cleanup spec Query API cannot return tokens; the toString() inconsistency is not worth fixing since callers have no reason to access tokenizedData programmatically on query results. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add implementation plan for Java SDK nomenclature cleanup 5-task TDD plan covering credential field renames with fallback, skyflow_id normalisation in Get/Query responses, and QueryResponse getErrors() accessor. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: accept clientId/keyId/tokenUri in BearerToken with fallback to old form Add fallback lookup logic so getBearerTokenFromCredentials tries new camelCase keys (clientId, keyId, tokenUri) first and falls back to the legacy all-caps forms (clientID, keyID, tokenURI) for backward compatibility during migration. Add testBearerTokenWithNewFormCredentialKeys to verify the new key form is recognized end-to-end. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: accept clientId/keyId in SignedDataTokens with fallback to old form Add fallback logic to GenerateSignedTokensFromCredentials so both new-form keys (clientId/keyId) and legacy all-caps keys (clientID/keyID) are accepted during migration. Mirrors the pattern already applied to BearerToken.java. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: normalise skyflow_id to skyflowId in Get and Query response maps Insert and Update responses already used camelCase skyflowId; Get and Query were passing through the raw wire-format snake_case key. Add the rename in getFormattedGetRecord and getFormattedQueryRecord, and add reflection-based unit tests to cover both formatters. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: add getErrors() accessor to QueryResponse Adds a private final errors field (always null) and its public accessor to QueryResponse, matching the pattern in GetResponse and InsertResponse. Removes the hardcoded responseObject.add("errors", null) from toString() since serializeNulls on the declared field handles it automatically. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: audit confirms no setFooID/getFooID violations in public API * chore: add Claude Code setup (CLAUDE.md + .claude/) Adapted from skyflow-node PR #305. Includes: - CLAUDE.md with project overview, structure, naming conventions, build commands - .claude/settings.json with PostToolUse compile+checkstyle hooks, PreToolUse generated-code guard, Stop notification; paths are relative (no hardcoded user dirs) - .claude/commands/: code-review, code-security, sdk-sample, test slash commands Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: fix gaps and inaccuracies in Claude setup files - CLAUDE.md: add vault/bin/ package, all 5 controllers, pre-existing test failure baseline - settings.json: fix checkstyle hook to print violations (was silently swallowing output with capture_output=True) - sdk-sample.md: fix InsertOptions (doesn't exist), correct sample package structure, correct credential type per feature - code-review.md: fix validation location (controller not build()), fix HashMap rule (SDK pattern is raw HashMaps) - test.md: document pre-existing failures, note checkstyle failsOnError Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add v2 backward compat + deprecation warnings implementation plan 6-task TDD plan: restore skyflow_id key alongside skyflowId in Get/Query responses, add WARN deprecation logs for old credential fields, Javadoc on affected response methods. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: update deprecation messages to say 'upcoming release' not 'v3' Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add [DEPRECATED] prefix to deprecation log messages per industry standard Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add PM-facing document for v2 public interface changes and deprecation Non-technical overview of credential field renames and skyflow_id response key deprecation — covers customer impact, deprecation warnings, migration guide, and what is NOT changing. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add downloadURL→downloadUrl deprecation to plan and PM doc - Deprecation plan: add Task 6 for GetRequest + DetokenizeRequest with @deprecated annotation approach (compile-time signal vs runtime log) - PM doc: add section 3 for downloadURL rename with migration example Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: remove SDK-level field value null/empty validation from Insert and Update The Skyflow API accepts additionalProperties of Any type including null and empty strings. SDK should not add validation on top of BE — pass through and let BE decide. Removed: - value == null/isEmpty check in validateInsertRequest - value == null/isEmpty check in validateUpdateRequest - value == null/isEmpty check in validateTokensMapWithTokenStrict - values.isEmpty() check in validateInsertRequest (no minItems in API spec) Kept: - values == null check (NPE guard — cannot iterate null array) - key == null/isEmpty check (null keys cannot be JSON-serialized) Deleted 6 tests that asserted on the removed behaviour. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add IDE autocomplete behavior for deprecation signals - PM doc: new section explaining how @deprecated(forRemoval) shows in IntelliJ/VS Code autocomplete (strikethrough, orange underline, tooltip with clickable link to new method) vs runtime WARN log for map keys - Deprecation plan: update downloadURL tasks to use @deprecated(since="2.1", forRemoval=true) + {link} for stronger IDE signal Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: segregate code smells into dedicated section in code-review command Splits old Section 6 into: - Section 6: Code quality (actionable correctness checks) - Section 7: Code smells (structural signals, flagged at Smell severity) Code smell catalogue covers: long methods/classes, business logic in data classes, toString() with logic, deep nesting, magic numbers, raw HashMap chains, dead code, stale comments, temporary fields. Severity table clarified: Critical/Bug/Edge Case/Quality = fix before merge; Smell = flag and track. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: add DEPRECATED_SKYFLOW_ID_KEY log entry to InfoLogs * fix: restore skyflow_id key in Get/Query responses for v2 backward compat Both skyflow_id (deprecated) and skyflowId (new form) are now present in response maps simultaneously. WARN log emitted per record. * docs: add deprecation Javadoc for skyflow_id key in GetResponse and QueryResponse * feat: deprecate downloadURL in favour of downloadUrl in GetRequest and DetokenizeRequest Old downloadURL() methods kept as @deprecated(forRemoval=true) delegates. Runtime WARN log emitted on old form usage. 100% test coverage: new form, deprecated form, default value for both classes. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: update CLAUDE.md — add code-smell command, update slash commands Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: update deprecation plan and PM doc - credentials permanently supported Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test: add new-form downloadUrl tests alongside deprecated downloadURL tests Both old (downloadURL) and new (downloadUrl) builder methods tested: - GetTests: 2 new tests for downloadUrl() with cross-assertion on deprecated getDownloadURL() returning same value - DetokenizeTests: 1 new test same pattern - VaultClientTests: 1 new integration test for DetokenizeRequest.downloadUrl() Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: changes to claude * docs: migrate V1-to-V2 guide from README to docs/, update CHANGELOG - Extract 260-line migration section from README.md to docs/migrate_to_v2.md following the pattern established in skyflow-node PR #258 - README now links to docs/migrate_to_v2.md instead of inline content - docs/migrate_to_v2.md adds v2.1+ sections for credential field renames and skyflow_id deprecation (new content) - CHANGELOG.md: add v2.0.4 release notes covering nomenclature changes, backward compat deprecations, and validation removal Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: simplify CHANGELOG — remove v1 entries, keep only v2.0.4 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: simplify CHANGELOG to point to GitHub and Maven releases Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add v2 banner to README with migration link and EOL notice Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: use release notes link instead of CHANGELOG in banner Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: update README banner to v2.1.x announcement Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * revert: remove .claude/ and CLAUDE.md — will be raised as separate PR from main Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: remove superpowers planning docs from repo Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: ignore docs/superpowers/ — keep planning docs local only Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: update cspell config — British English words, Maven flags, ignore paths Added words: serialise/d/s, normalise/d/s/Normalises, behaviour/s/Behaviour, sanitisation, recognised, unrecognised, prioritised Added regex: /-D[A-Za-z][A-Za-z0-9.]*/g to ignore Maven -D flags Added ignorePaths: RUNNING_SAMPLES.md, docs/superpowers/** Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: remove v2-public-interface-changes.md Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: replace real RSA key with fake key in BearerTokenTests Real 2048-bit RSA key replaced with fake base64 value. Assertion updated from InvalidTokenUri to InvalidKeySpec — still proves all credential fields were resolved (failure is at RSA parsing, not field lookup). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: guard against null in DetokenizeRequest.downloadUrl(null) Calling .downloadUrl(null) previously stored null in the field, creating an NPE risk for callers who read getDownloadUrl() back without a null check. Now null -> false (matching the default), consistent with the continueOnError(null) guard in the same builder. Added test: testDetokenizeRequestDownloadUrlNullTreatedAsFalse Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: remove dead error constants after validation removal EmptyValues, EmptyValueInValues, EmptyValueInTokens (ErrorMessage) and EMPTY_VALUES, EMPTY_OR_NULL_VALUE_IN_VALUES, EMPTY_OR_NULL_VALUE_IN_TOKENS (ErrorLogs) are unreachable since the SDK-level null/empty field validation was removed. Deleted to prevent accidental re-wiring. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Revert "fix: guard against null in DetokenizeRequest.downloadUrl(null)" This reverts commit a802668. * test: add positive tests for permissive Insert validation behaviour Three new tests assert that previously-blocked inputs now pass SDK validation (SDK defers to BE per API spec additionalProperties: Any type): - Empty values array [] passes - Null field value passes - Empty string field value passes Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix banner * feat: port PR #273 changes — raw body support, URL encoding, null-safe request ID - Add EMPTY_STRING, QUOTE, HTTPS_PROTOCOL, CURLY_PLACEHOLDER and HttpUtilityExtra (RAW_BODY_KEY, SDK_GENERATED_PREFIX) to Constants - HttpUtility: conditional content-type header, __raw_body__ passthrough, UUID fallback when server omits x-request-id, URL-encoded form params - Utils: URL-encode path and query params with graceful fallback - Validations: accept String request bodies for non-JSON content types - ConnectionController: wrap String bodies in __raw_body__ for non-JSON content types; fall back to raw string when response is not JSON - InfoLogs: "Bearer token is expired" → "Bearer token is invalid or expired" - HttpUtilityTests: add raw body, no content-type, null request ID and special-character form-encoding tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: guard unsafe Optional.get() calls and remove dead statement in DetectController - Remove dead statement `response.getEntities().get(0).getFile()` that discarded its result and threw IndexOutOfBoundsException/NPE when entities was null or empty (C1); the guarded block below already handles entity processing correctly - Replace unguarded `response.getOutput().get()` in getFirstOutput() with `.orElse(null)` so an absent output Optional returns null instead of throwing NoSuchElementException (C2) - Replace unguarded `firstOutput.getProcessedFileExtension().get().toString()` with `.map(Object::toString).orElse(UNKNOWN)`, reusing the already-computed Optional and matching the safe pattern used for processedFileType one line above (C3) - Add 4 unit tests covering all three fixes via reflection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: guard Optional.get() in delete, close FileReader, guard empty form-encode - VaultController.delete: replace unguarded getRecordIdResponse().get() with .orElse(Collections.emptyList()) so an absent field in the API response returns an empty list instead of throwing NoSuchElementException (M2) - BearerToken, SignedDataTokens: wrap FileReader in try/finally to guarantee close() is called even when JsonParser throws JsonSyntaxException; close IOException is intentionally swallowed since the parse already completed (M3) - HttpUtility.formatJsonToFormEncodedString: guard against empty entry set so substring(0, -1) is not called on an empty StringBuilder (M4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: update README for v2.1 public interface changes - Replace deprecated credential key names in examples: clientID→clientId, keyID→keyId, TokenURI→tokenUri - Update Insert, Get, and Query response examples to use skyflowId (the SDK has always returned skyflowId for Insert; Get/Query now return skyflowId as the primary key) - Add deprecation note after each affected response block: skyflow_id is deprecated and will be removed in an upcoming release Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: do not generate SDK-side requestId when server omits x-request-id header When the response carries no x-request-id header, set requestID to null instead of fabricating a UUID with an SDK-Generated- prefix. A null value is the honest signal to callers that no server request ID is available. Also removes the now-unused UUID import. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add printWarningLogOnce to suppress per-record deprecation spam Replace per-record printWarningLog calls for DEPRECATED_SKYFLOW_ID_KEY with printWarningLogOnce so the deprecation notice fires at most once per JVM session regardless of result set size. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: fix testSendRequestWithNullRequestId to assert null requestId The SDK no longer generates a fallback UUID when the server omits x-request-id; getRequestID() now returns null. Update the assertion to match the new behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: fix request_index to requestIndex in README response examples The SDK puts camelCase requestIndex in response maps; the README examples incorrectly showed snake_case request_index. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: readme * docs: add v2.1.0 upgrade banner and migration guide to v1 README (#310) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: accept camelCase skyflowId in UpdateRequest and fire deprecation warnings per-request - UpdateRequest.data now accepts 'skyflowId' (preferred) alongside deprecated 'skyflow_id' - extractUpdateSkyflowId() prefers camelCase key, emits WARN-level deprecation on snake_case fallback - Remove printWarningLogOnce — all deprecation warnings now fire on every request via printWarningLog - Add DEPRECATED_SKYFLOW_ID_REQUEST_KEY log entry for request-side key deprecation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Revert "docs: add v2.1.0 upgrade banner and migration guide to v1 README (#310)" (#311) This reverts commit 6727625. * feat: prefer skyflowId over skyflow_id in UpdateRequest with deprecation warning When both keys are present, skyflowId is used and a warning is emitted. Adds unit tests for all four cases: camelCase only, snake_case only, both keys (preference), and both keys (map cleanup). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add getByot() as canonical form; keep getBYOT() as deprecated delegate - TokenMode.getByot() is the new preferred accessor; getBYOT() retained as @deprecated(since="2.1",forRemoval=true) delegate for back-compat - VaultClient updated to call getByot() at all three insert/update sites - UpdateTests: 5 new tests covering camelCase skyflowId key in UpdateRequest - LogUtilLevelTests: 5 new tests verifying WARN log fires at DEBUG/INFO/WARN levels and is suppressed at ERROR (matches Skyflow default) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add TokenModeTest covering getByot(), deprecated getBYOT() delegate, and toString() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: emit runtime deprecation warning in getBYOT(); test that it fires - InfoLogs: add DEPRECATED_GET_BYOT constant - TokenMode.getBYOT(): call LogUtil.printWarningLog() so callers see a runtime warning (consistent with downloadURL() and other deprecated methods) - TokenModeTest: replace @SuppressWarnings-only tests with assertions that the warning fires at INFO level and is suppressed at ERROR level Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: consolidate both-keys deprecation warning into DEPRECATED_SKYFLOW_ID_REQUEST_KEY Remove DEPRECATED_SKYFLOW_ID_BOTH_KEYS; both the snake_case-only and both-keys-present paths in extractUpdateSkyflowId now emit the same DEPRECATED_SKYFLOW_ID_REQUEST_KEY message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: deprecate updateLogLevel(); add setLogLevel() as canonical replacement - Skyflow.setLogLevel() is the new preferred method on the live client - updateLogLevel() retained as @deprecated(since="2.1",forRemoval=true) delegate; emits runtime warning via DEPRECATED_UPDATE_LOG_LEVEL log - InfoLogs: add DEPRECATED_UPDATE_LOG_LEVEL constant - SkyflowException: add JavaDoc documenting validation vs API error paths, null behaviour of requestId/grpcCode, and typical catch pattern - SkyflowTests: add setLogLevel test, deprecation-warning-fires test, and warning-suppressed-at-ERROR test for updateLogLevel() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * samples: migrate UpdateExample to skyflowId; preserve deprecated skyflow_id form - UpdateExample.java: replace skyflow_id with skyflowId in both data maps - deprecated/UpdateExample.java: retain original skyflow_id pattern with @deprecated annotation and pointer to the current example Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: update migration guide with v2.1 changes Add sections for UpdateRequest skyflowId preference, updateLogLevel and getBYOT deprecations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: achieve 100% instruction/branch coverage on all public interfaces Closes all reachable coverage gaps across 16 test files. Adds tests for detect response types (ReidentifyTextResponse, DeidentifyTextResponse, EntityInfo, TextIndex, TokenFormat), enum values (DetectOutputTranscriptions, DetectEntities, MaskingMethod, TokenType, DeidentifyFileStatus), InvokeConnectionResponse.getErrors(), DeidentifyFileRequest/Response constructors and builder null-handling, SkyflowException missing JSON branches, InsertRequest.continueOnError(false), and VaultConfig/ ConnectionConfig null-credentials fallback. Two residual JaCoCo false negatives remain: Optional.filter lambda in DetokenizeRecordResponse (unreachable null check) and dead ternary branches in Skyflow$SkyflowClientBuilder whose guards are always non-null after validation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: emit DEPRECATED_SKYFLOW_ID_KEY warning in getFormattedBatchInsertRecord getFormattedGetRecord and getFormattedQueryRecord both emit the deprecation warning when the API response contains the snake_case skyflow_id key. getFormattedBatchInsertRecord was inconsistent — it silently mapped skyflow_id to skyflowId with no warning. Also future-proofs the batch insert path to handle camelCase skyflowId if the API wire format ever migrates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: add Error Handling section to README Documents SkyflowException — its two categories (validation vs API errors), all six properties (httpCode, message, httpStatus, grpcCode, requestId, details), the recommended try/catch pattern, and a table distinguishing what is null/empty for validation errors versus API errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: remove redundant null-for-validation-errors notes from property table The subsection below the table already covers null/empty behaviour for validation errors; repeating it inline on every row was noise. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent ccc5998 commit 3e9f105

24 files changed

Lines changed: 799 additions & 5 deletions

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ The Skyflow Java SDK is designed to help with integrating Skyflow into a Java ba
5050
- [Generate scoped bearer tokens](#generate-scoped-bearer-tokens)
5151
- [Generate signed data tokens](#generate-signed-data-tokens)
5252
- [Bearer token expiry edge case](#bearer-token-expiry-edge-case)
53+
- [Error Handling](#error-handling)
54+
- [Catching SkyflowException](#catching-skyflowexception)
55+
- [SkyflowException properties](#skyflowexception-properties)
5356
- [Logging](#logging)
5457
- [Reporting a Vulnerability](#reporting-a-vulnerability)
5558

@@ -2786,6 +2789,48 @@ public class DetokenizeExample {
27862789
}
27872790
```
27882791

2792+
# Error Handling
2793+
2794+
The SDK uses `SkyflowException` for all errors — both client-side validation errors and server-side API errors.
2795+
2796+
## Catching SkyflowException
2797+
2798+
Wrap SDK calls in a `try/catch` block and catch `SkyflowException` to handle Skyflow-specific errors separately from unexpected exceptions:
2799+
2800+
```java
2801+
import com.skyflow.errors.SkyflowException;
2802+
2803+
try {
2804+
InsertResponse response = skyflowClient.vault().insert(insertRequest);
2805+
} catch (SkyflowException e) {
2806+
System.err.println("Skyflow error:");
2807+
System.err.println(" HTTP code : " + e.getHttpCode());
2808+
System.err.println(" Message : " + e.getMessage());
2809+
System.err.println(" Request ID: " + e.getRequestId());
2810+
System.err.println(" Details : " + e.getDetails());
2811+
} catch (Exception e) {
2812+
System.err.println("Unexpected error: " + e.getMessage());
2813+
}
2814+
```
2815+
2816+
## SkyflowException properties
2817+
2818+
| Property | Method | Description |
2819+
|---|---|---|
2820+
| HTTP status code | `getHttpCode()` | Integer status code (e.g. `400`, `404`, `500`). |
2821+
| Message | `getMessage()` | Human-readable description of the error. |
2822+
| HTTP status string | `getHttpStatus()` | Status string from the server (e.g. `"BAD_REQUEST"`). |
2823+
| gRPC code | `getGrpcCode()` | gRPC status code from the server. |
2824+
| Request ID | `getRequestId()` | The `x-request-id` header — useful for support escalations. |
2825+
| Details | `getDetails()` | `JsonArray` of additional error context from the server. Empty array for validation errors, `null` if the server response omitted the field. |
2826+
2827+
**Validation errors** (missing table name, empty token list, etc.) are thrown before any network call:
2828+
- `httpCode` is always `400`
2829+
- `requestId` and `grpcCode` are `null`
2830+
- `details` is an empty array
2831+
2832+
**API errors** are returned by the Skyflow server and have all fields populated from the response body and headers.
2833+
27892834
# Logging
27902835

27912836
The SDK provides logging with Java's built-in logging library. By default, the SDK's logging level is set to `LogLevel.ERROR`. This can be changed using the `setLogLevel(logLevel)` method, as shown below:

docs/migrate_to_v2.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,35 @@ Response maps now return `skyflowId` (camelCase). The legacy `skyflow_id` key is
249249

250250
---
251251

252+
## Update request data key (v2.1+)
253+
254+
When calling `update()`, use `skyflowId` (camelCase) as the key in the data map to identify the record. Using `skyflow_id` still works but emits a deprecation warning. If both keys are present, `skyflowId` takes precedence.
255+
256+
```java
257+
HashMap<String, Object> data = new HashMap<>();
258+
data.put("skyflowId", "<SKYFLOW_ID>"); // preferred
259+
data.put("card_number", "<NEW_VALUE>");
260+
261+
UpdateRequest request = UpdateRequest.builder()
262+
.table("<TABLE_NAME>")
263+
.data(data)
264+
.returnTokens(true)
265+
.build();
266+
267+
skyflowClient.vault().update(request);
268+
```
269+
270+
---
271+
272+
## Method renames (v2.1+)
273+
274+
The following instance methods have been renamed for consistency. The old names still work but emit deprecation warnings.
275+
276+
| Deprecated | Preferred |
277+
|---|---|
278+
| `skyflowClient.updateLogLevel(logLevel)` | `skyflowClient.setLogLevel(logLevel)` |
279+
| `TokenMode.getBYOT()` | `TokenMode.getByot()` |
280+
281+
---
282+
252283
For the full list of changes see [CHANGELOG.md](../CHANGELOG.md).

samples/src/main/java/com/example/vault/UpdateExample.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static void main(String[] args) throws SkyflowException {
4646
// Step 5: Update records with TokenMode enabled
4747
try {
4848
HashMap<String, Object> data1 = new HashMap<>();
49-
data1.put("skyflow_id", "<YOUR_SKYFLOW_ID>"); // Replace with the Skyflow ID of the record
49+
data1.put("skyflowId", "<YOUR_SKYFLOW_ID>"); // Replace with the Skyflow ID of the record
5050
data1.put("<COLUMN_NAME_1>", "<COLUMN_VALUE_1>"); // Replace with column name and value to update
5151
data1.put("<COLUMN_NAME_2>", "<COLUMN_VALUE_2>"); // Replace with another column name and value
5252

@@ -71,7 +71,7 @@ public static void main(String[] args) throws SkyflowException {
7171
// Step 6: Update records with TokenMode disabled
7272
try {
7373
HashMap<String, Object> data2 = new HashMap<>();
74-
data2.put("skyflow_id", "<YOUR_SKYFLOW_ID>"); // Replace with the Skyflow ID of the record
74+
data2.put("skyflowId", "<YOUR_SKYFLOW_ID>"); // Replace with the Skyflow ID of the record
7575
data2.put("<COLUMN_NAME_1>", "<COLUMN_VALUE_1>"); // Replace with column name and value to update
7676
data2.put("<COLUMN_NAME_2>", "<COLUMN_VALUE_2>"); // Replace with another column name and value
7777

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.example.vault.deprecated;
2+
3+
import com.skyflow.Skyflow;
4+
import com.skyflow.config.Credentials;
5+
import com.skyflow.config.VaultConfig;
6+
import com.skyflow.enums.Env;
7+
import com.skyflow.enums.LogLevel;
8+
import com.skyflow.enums.TokenMode;
9+
import com.skyflow.errors.SkyflowException;
10+
import com.skyflow.vault.data.UpdateRequest;
11+
import com.skyflow.vault.data.UpdateResponse;
12+
13+
import java.util.HashMap;
14+
15+
/**
16+
* @deprecated Pre-v2.1 pattern. The "skyflow_id" key in the data map is deprecated.
17+
* Use "skyflowId" instead (see {@link com.example.vault.UpdateExample}).
18+
*
19+
* This example is retained for reference during the deprecation window.
20+
* "skyflow_id" still works but emits a runtime warning and will be removed in a future release.
21+
*/
22+
@Deprecated
23+
public class UpdateExample {
24+
public static void main(String[] args) throws SkyflowException {
25+
// Step 1: Set up credentials for the first vault configuration
26+
Credentials credentials = new Credentials();
27+
credentials.setApiKey("<YOUR_API_KEY>"); // Replace with the actual API key
28+
29+
// Step 2: Configure the vault
30+
VaultConfig vaultConfig = new VaultConfig();
31+
vaultConfig.setVaultId("<YOUR_VAULT_ID_1>"); // Replace with the ID of the vault
32+
vaultConfig.setClusterId("<YOUR_CLUSTER_ID_1>"); // Replace with the cluster ID of the vault
33+
vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD)
34+
vaultConfig.setCredentials(credentials); // Associate the credentials with the vault
35+
36+
// Step 3: Set up credentials for the Skyflow client
37+
Credentials skyflowCredentials = new Credentials();
38+
skyflowCredentials.setCredentialsString("<YOUR_CREDENTIALS_STRING>"); // Replace with the actual credentials string
39+
40+
// Step 4: Create a Skyflow client and add vault configurations
41+
Skyflow skyflowClient = Skyflow.builder()
42+
.setLogLevel(LogLevel.ERROR) // Enable debugging for detailed logs
43+
.addVaultConfig(vaultConfig) // Add the vault configuration
44+
.addSkyflowCredentials(skyflowCredentials) // Add general Skyflow credentials
45+
.build();
46+
47+
// Step 5: Update records with TokenMode enabled
48+
// DEPRECATED: use "skyflowId" key instead of "skyflow_id"
49+
try {
50+
HashMap<String, Object> data1 = new HashMap<>();
51+
data1.put("skyflow_id", "<YOUR_SKYFLOW_ID>"); // @deprecated — use "skyflowId"
52+
data1.put("<COLUMN_NAME_1>", "<COLUMN_VALUE_1>"); // Replace with column name and value to update
53+
data1.put("<COLUMN_NAME_2>", "<COLUMN_VALUE_2>"); // Replace with another column name and value
54+
55+
HashMap<String, Object> tokens = new HashMap<>();
56+
tokens.put("<COLUMN_NAME_2>", "<TOKEN_VALUE_2>"); // Replace with the token for COLUMN_NAME_2
57+
58+
UpdateRequest updateRequest1 = UpdateRequest.builder()
59+
.table("<TABLE_NAME>") // Replace with the table name
60+
.tokenMode(TokenMode.ENABLE) // Enable TokenMode for token validation
61+
.data(data1) // Data to update
62+
.tokens(tokens) // Provide tokens for TokenMode columns
63+
.returnTokens(true) // Return tokens along with the update response
64+
.build();
65+
66+
UpdateResponse updateResponse1 = skyflowClient.vault().update(updateRequest1); // Perform the update
67+
System.out.println("Update Response (TokenMode Enabled): " + updateResponse1);
68+
} catch (SkyflowException e) {
69+
System.out.println("Error during update with TokenMode enabled:");
70+
e.printStackTrace();
71+
}
72+
73+
// Step 6: Update records with TokenMode disabled
74+
// DEPRECATED: use "skyflowId" key instead of "skyflow_id"
75+
try {
76+
HashMap<String, Object> data2 = new HashMap<>();
77+
data2.put("skyflow_id", "<YOUR_SKYFLOW_ID>"); // @deprecated — use "skyflowId"
78+
data2.put("<COLUMN_NAME_1>", "<COLUMN_VALUE_1>"); // Replace with column name and value to update
79+
data2.put("<COLUMN_NAME_2>", "<COLUMN_VALUE_2>"); // Replace with another column name and value
80+
81+
UpdateRequest updateRequest2 = UpdateRequest.builder()
82+
.table("<TABLE_NAME>") // Replace with the table name
83+
.tokenMode(TokenMode.DISABLE) // Disable TokenMode
84+
.data(data2) // Data to update
85+
.returnTokens(false) // Do not return tokens
86+
.build();
87+
88+
UpdateResponse updateResponse2 = skyflowClient.vault().update(updateRequest2); // Perform the update
89+
System.out.println("Update Response (TokenMode Disabled): " + updateResponse2);
90+
} catch (SkyflowException e) {
91+
System.out.println("Error during update with TokenMode disabled:" + e);
92+
}
93+
}
94+
}

src/main/java/com/skyflow/Skyflow.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,18 @@ public Skyflow updateSkyflowCredentials(Credentials credentials) throws SkyflowE
7474
return this;
7575
}
7676

77-
public Skyflow updateLogLevel(LogLevel logLevel) {
77+
public Skyflow setLogLevel(LogLevel logLevel) {
7878
this.builder.setLogLevel(logLevel);
7979
return this;
8080
}
8181

82+
/** @deprecated Use {@link #setLogLevel(LogLevel)} instead. */
83+
@Deprecated(since = "2.1", forRemoval = true)
84+
public Skyflow updateLogLevel(LogLevel logLevel) {
85+
LogUtil.printWarningLog(InfoLogs.DEPRECATED_UPDATE_LOG_LEVEL.getLog());
86+
return setLogLevel(logLevel);
87+
}
88+
8289
public LogLevel getLogLevel() {
8390
return this.builder.logLevel;
8491
}

src/main/java/com/skyflow/errors/SkyflowException.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@
99
import java.util.List;
1010
import java.util.Map;
1111

12+
/**
13+
* Exception thrown by all Skyflow SDK operations.
14+
*
15+
* <p>There are two broad categories of errors:
16+
*
17+
* <ul>
18+
* <li><b>Validation errors</b> — caught before any network call is made (e.g. missing table,
19+
* empty token list). These always have {@code httpCode = 400} and an empty
20+
* {@link #getDetails()} array. {@link #getRequestId()} and {@link #getGrpcCode()} are
21+
* {@code null}.
22+
* <li><b>API errors</b> — returned by the Skyflow server. The HTTP status code, gRPC code,
23+
* human-readable status string, error message, and request ID are all parsed from the
24+
* response and available via the corresponding getters.
25+
* </ul>
26+
*
27+
* <p>Typical error-handling pattern:
28+
* <pre>{@code
29+
* try {
30+
* InsertResponse response = vault.insert(request);
31+
* } catch (SkyflowException e) {
32+
* System.err.println("HTTP " + e.getHttpCode() + " — " + e.getMessage());
33+
* if (e.getRequestId() != null) {
34+
* System.err.println("Request ID: " + e.getRequestId());
35+
* }
36+
* }
37+
* }</pre>
38+
*/
1239
public class SkyflowException extends Exception {
1340
private String requestId;
1441
private Integer grpcCode;
@@ -33,6 +60,11 @@ public SkyflowException(String message, Throwable cause) {
3360
this.message = message;
3461
}
3562

63+
/**
64+
* Constructs a validation error with a fixed HTTP 400 status.
65+
* {@link #getDetails()} returns an empty array; {@link #getRequestId()} and
66+
* {@link #getGrpcCode()} return {@code null}.
67+
*/
3668
public SkyflowException(int code, String message) {
3769
super(message);
3870
this.httpCode = code;
@@ -41,6 +73,13 @@ public SkyflowException(int code, String message) {
4173
this.details = new JsonArray();
4274
}
4375

76+
/**
77+
* Constructs an API error from an HTTP response.
78+
* Parses the JSON error body to populate {@link #getMessage()}, {@link #getGrpcCode()},
79+
* {@link #getHttpStatus()}, and {@link #getDetails()}. The request ID is read from the
80+
* {@code x-request-id} response header. If the body cannot be parsed, falls back to the
81+
* raw body string as the message.
82+
*/
4483
public SkyflowException(int httpCode, Throwable cause, Map<String, List<String>> responseHeaders, String responseBody) {
4584
super(cause);
4685
this.httpCode = httpCode > 0 ? httpCode : 400;
@@ -65,6 +104,10 @@ private void setResponseBody(String responseBody, Map<String, List<String>> resp
65104
}
66105
}
67106

107+
/**
108+
* Returns the {@code x-request-id} from the server response, useful for support escalations.
109+
* {@code null} for validation errors that never reached the server.
110+
*/
68111
public String getRequestId() {
69112
return requestId;
70113
}
@@ -89,10 +132,19 @@ private void setHttpStatus() {
89132
this.httpStatus = statusElement == null ? null : statusElement.getAsString();
90133
}
91134

135+
/**
136+
* Returns the HTTP status code (e.g. 400, 404, 500).
137+
* Defaults to 400 when the server returned a non-positive code.
138+
*/
92139
public int getHttpCode() {
93140
return httpCode;
94141
}
95142

143+
/**
144+
* Returns additional error details from the server response, or an empty array for
145+
* validation errors. Never {@code null} for validation errors; may be {@code null} for
146+
* API errors whose response body contained no {@code details} field.
147+
*/
96148
public JsonArray getDetails() {
97149
return details;
98150
}
@@ -112,10 +164,18 @@ private void setDetails(Map<String, List<String>> responseHeaders) {
112164
}
113165
}
114166

167+
/**
168+
* Returns the gRPC status code from the server response.
169+
* {@code null} for validation errors and API responses that omit this field.
170+
*/
115171
public Integer getGrpcCode() {
116172
return grpcCode;
117173
}
118174

175+
/**
176+
* Returns the human-readable HTTP status string from the server response (e.g.
177+
* {@code "Bad Request"}, {@code "Not Found"}).
178+
*/
119179
public String getHttpStatus() {
120180
return httpStatus;
121181
}

src/main/java/com/skyflow/logs/InfoLogs.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ public enum InfoLogs {
100100
DEPRECATED_SKYFLOW_ID_KEY("[DEPRECATED] Response key 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead."),
101101
DEPRECATED_SKYFLOW_ID_REQUEST_KEY("[DEPRECATED] Request data key 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead."),
102102
DEPRECATED_DOWNLOAD_URL("[DEPRECATED] Method 'downloadURL()' is deprecated and will be removed in an upcoming release. Use 'downloadUrl()' instead."),
103-
DEPRECATED_GET_BYOT("[DEPRECATED] Method 'getBYOT()' is deprecated and will be removed in an upcoming release. Use 'getByot()' instead.");
103+
DEPRECATED_GET_BYOT("[DEPRECATED] Method 'getBYOT()' is deprecated and will be removed in an upcoming release. Use 'getByot()' instead."),
104+
DEPRECATED_UPDATE_LOG_LEVEL("[DEPRECATED] Method 'updateLogLevel()' is deprecated and will be removed in an upcoming release. Use 'setLogLevel()' instead.");
104105

105106

106107

src/main/java/com/skyflow/vault/controller/VaultController.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ private static synchronized HashMap<String, Object> getFormattedBatchInsertRecor
103103
if (records != null) {
104104
for (JsonElement recordElement : records) {
105105
JsonObject recordObject = recordElement.getAsJsonObject();
106-
insertRecord.put("skyflowId", recordObject.get("skyflow_id").getAsString());
106+
if (recordObject.has("skyflowId")) {
107+
insertRecord.put("skyflowId", recordObject.get("skyflowId").getAsString());
108+
} else if (recordObject.has("skyflow_id")) {
109+
insertRecord.put("skyflowId", recordObject.get("skyflow_id").getAsString());
110+
LogUtil.printWarningLog(InfoLogs.DEPRECATED_SKYFLOW_ID_KEY.getLog());
111+
}
107112
JsonElement tokensElement = recordObject.get("tokens");
108113
if (tokensElement != null) {
109114
insertRecord.putAll(tokensElement.getAsJsonObject().asMap());

0 commit comments

Comments
 (0)