{"slug": "show-hn-typesafe-java-sdk-unofficial", "title": "Show HN: Typesafe Java SDK (Unofficial)", "summary": "A developer has released an unofficial, community-built Java 21+ SDK for TypeSafe AI's System One API (POST /v1/systemone), offering sync and async calls, automatic retries with exponential backoff and jitter on 429/5xx responses, and client-side validation via InvalidRequestException. The Maven-built client uses java.net.http.HttpClient for transport with Jackson as its only runtime dependency, and requires a model to be set per-request or via defaultModel. The project is not affiliated with, endorsed by, or sponsored by TypeSafe AI, and its author warns the API may change without notice.", "body_md": "**Unofficial community SDK.** This project is not affiliated with, endorsed\nby, or sponsored by TypeSafe AI. \"TypeSafe\" and all related names, marks, and\nlogos are trademarks of TypeSafe AI - used here solely to identify the API\nthis client targets. Use at your own risk; the API may change without notice.\n\nJava client for [TypeSafe AI](https://api.typesafe.ai)'s **System One** API\n(`POST /v1/systemone`).\n\n- Java 21+, Maven build\n- `java.net.http.HttpClient` transport - no extra HTTP dependency\n- Jackson for JSON (only runtime dependency)\n- Sync + async (`CompletableFuture` ) APIs\n- Automatic retries with exponential backoff + jitter on 429/5xx, honoring\n`Retry-After`\n- Sealed `Question` /`Answer` hierarchies; unknown answer types degrade to a\nforward-compatible`UnknownAnswer`\n\n``` python\nimport dev.dosa.typesafe.TypeSafeClient;\nimport dev.dosa.typesafe.model.*;\n\nTypeSafeClient client = TypeSafeClient.builder()\n        .apiKey(System.getenv(\"TYPESAFE_API_KEY\"))\n        .defaultModel(\"jev-latest\")        // used when a request sets no model\n        .requestTimeout(Duration.ofSeconds(10))\n        .build();\n\nSystemOneRequest request = SystemOneRequest.builder()\n        .state(Map.of(\"ticket\", \"I want a refund now!\"))\n        .question(\"urgent\", Question.noul(\"Does this convey urgency?\")\n                .whenTrue(\"Explicitly time-sensitive\")\n                .whenFalse(\"No time pressure\"))\n        .question(\"dept\", Question.choice(\"Which team?\")\n                .option(\"billing\", \"Payment issues\")\n                .option(\"technical\", \"Bugs\"))\n        .question(\"frustration\", Question.score(\"How frustrated?\")\n                .level(\"Calm\")\n                .level(\"Frustrated\")\n                .level(\"Very angry\"))\n        .build();\n\nSystemOneResponse resp = client.systemOne(request);                    // sync\n// CompletableFuture<SystemOneResponse> f = client.systemOneAsync(request); // async\n\nresp.noul(\"urgent\").ifPresent(p -> System.out.println(\"urgency: \" + p));\nresp.choice(\"dept\").ifPresent(c -> System.out.println(\"team: \" + c.choice()));\nresp.score(\"frustration\").ifPresent(s -> System.out.println(\"score: \" + s.score()));\n```\n\nOne static factory + fluent builder per question type:\n\n```\nQuestion.noul(\"Does this convey urgency?\")\n        .whenTrue(\"Explicitly time-sensitive\")\n        .whenFalse(\"No time pressure\");\n\nQuestion.choice(\"Which team?\")\n        .option(\"billing\", \"Payment issues\")\n        .option(\"technical\", \"Bugs\");\n\nQuestion.score(\"How frustrated?\")\n        .level(\"Calm\")\n        .level(\"Frustrated\")\n        .level(\"Very angry\");\n```\n\nValidation runs client-side in `SystemOneRequest.builder().build()`, before any\nnetwork call, and throws `InvalidRequestException`: empty questions map, blank\nquestion names, choice questions with fewer than 2 or more than 255 options,\nscore questions with fewer than 2 levels, blank instructions, and a `state`\nthat is not a JSON string, object, or array.\n\nA **model is required** by the API: set it per-request via `.model(\"jev-latest\")`\nor once on the client via `.defaultModel(...)`. If neither is set, the client\nthrows `InvalidRequestException` before sending. Discover valid names with:\n\n```\nList<ModelInfo> models = client.models();   // GET /v1/models\nmodels.forEach(m -> System.out.println(m.name() + \" - \" + m.description()));\nOptional<Double>        n = resp.noul(\"urgent\");        // NoulAnswer probability\nOptional<ChoiceAnswer>  c = resp.choice(\"dept\");\nOptional<ScoreAnswer>   s = resp.score(\"frustration\");\nOptional<Answer>        a = resp.answer(\"anything\");    // untyped lookup\n\nStream<Entry<String, NoulAnswer>>   allNouls   = resp.nouls();\nStream<Entry<String, ChoiceAnswer>> allChoices = resp.choices();\nStream<Entry<String, ScoreAnswer>>  allScores  = resp.scores();\n```\n\nResponse metadata (kept separate from answer data):\n\n```\nresp.requestId();   // Optional<String> - x-typesafe-request-id header\nresp.httpStatus();  // final HTTP status after retries\nresp.attempts();    // number of HTTP attempts made\nresp.usage();       // Optional<Usage> - input/output token counts\n```\n\nAll exceptions extend the unchecked `dev.dosa.typesafe.exception.TypeSafeException`:\n\n| Exception | When | \n|---|---|\n| `InvalidRequestException` | Client-side validation failure (thrown before any network) | \n| `AuthenticationException` | HTTP 401/403 - never retried | \n| `RateLimitException` | HTTP 429 after all retry attempts exhausted | \n| `ApiException` | Other error statuses / unparseable body (status + body + request id) | \n| `NetworkException` | Transport-level `IOException` (timeouts, DNS, refused) | \n\nThe API key is only ever sent in the `Authorization` header and never appears\nin exception messages.\n\n```\nTypeSafeClient.builder()\n        .apiKey(...)                     // or TYPESAFE_API_KEY env var\n        .baseUrl(...)                    // or TYPESAFE_BASE_URL env var, then https://api.typesafe.ai\n        .defaultModel(...)               // fallback model; one is required somewhere\n        .requestTimeout(Duration)        // per attempt, default 30s\n        .connectTimeout(Duration)        // default 10s\n        .maxAttempts(3)                  // retries incl. initial attempt\n        .initialRetryDelay(Duration)     // backoff base, default 200ms\n        .maxRetryDelay(Duration)         // backoff cap, default 10s\n        .httpClient(HttpClient)          // custom transport\n        .build();\nmvn test      # compiles and runs the test suite (JDK-local stub HTTP server)\nmvn package   # builds the jar\n```\n\nWith `TYPESAFE_API_KEY` set in the environment, `mvn test` also runs the live\nintegration tests in `TypeSafeClientLiveTest` against the real API (they are\nskipped otherwise).", "url": "https://wpnews.pro/news/show-hn-typesafe-java-sdk-unofficial", "canonical_source": "https://github.com/QAInsights/typesafe-java-sdk", "published_at": "2026-09-19 01:03:12+00:00", "updated_at": "2026-09-19 01:24:53.545304+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-products"], "entities": ["TypeSafe AI", "System One API", "Java 21", "Maven", "Jackson", "java.net.http.HttpClient", "InvalidRequestException", "CompletableFuture"], "alternates": {"html": "https://wpnews.pro/news/show-hn-typesafe-java-sdk-unofficial", "markdown": "https://wpnews.pro/news/show-hn-typesafe-java-sdk-unofficial.md", "text": "https://wpnews.pro/news/show-hn-typesafe-java-sdk-unofficial.txt", "jsonld": "https://wpnews.pro/news/show-hn-typesafe-java-sdk-unofficial.jsonld"}}