Java SDK
Install the official Java SDK and use chat, streaming, web search, agents, and semantic search in HiNow.
Updated on Aug 09, 2026
The official Java SDK for the HiNow API. Runs on Java 11 or newer and uses OkHttp and Gson, which are already present in most projects.
The API speaks the OpenAI protocol, and the SDK follows the same format. If you have already integrated with OpenAI, the shape of the calls is what you know.
The library is published via JitPack, so the repository must be declared alongside the dependency. The dependency alone is not enough.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.hinow-ai</groupId>
<artifactId>sdk-java</artifactId>
<version>v2.0.0</version>
</dependency>repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.hinow-ai:sdk-java:v2.0.0'
}repositories {
mavenCentral()
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.hinow-ai:sdk-java:v2.0.0")
}Store the key in HINOW_API_KEY and pass null to the constructor. Passing the key directly also works, but avoid leaving the value in the code.
export HINOW_API_KEY="hi_sua_chave_aqui"import ai.hinow.ChatCompletion;
import ai.hinow.ChatCompletionRequest;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.Message;
public class FirstCall {
public static void main(String[] args) throws HinowException {
// Passing null makes the client read the key from HINOW_API_KEY.
Hinow client = new Hinow(null);
ChatCompletion answer = client.chat().completions().create(
ChatCompletionRequest.builder()
.model("hinow/himax")
.addMessage(new Message("system", "You answer in English."))
.addMessage(new Message("user", "What is an API? Answer in one paragraph."))
.build());
// getText() gives you the string; getContent() holds the raw value.
System.out.println(answer.getChoices().get(0).getMessage().getText());
ChatCompletion.Usage uso = answer.getUsage();
System.out.printf("%ninput: %d · output: %d%n",
uso.getPromptTokens(), uso.getCompletionTokens());
}
}The hinow/ prefix is part of the model name
Sending himax instead of hinow/himax returns 404 model_not_found, and the message does not make it clear that the prefix is missing. This applies to all: hinow/himax, hinow/hinova, hinow/higenesis.
Read the response via getText()
getContent() returns Object, because a message can carry text or a list of parts. getText() handles both cases and returns String.
createStream delivers each chunk to your callback as soon as it arrives and returns when the model finishes. In a chat interface, this changes the perception of speed more than switching models.
import ai.hinow.ChatCompletionRequest;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.Message;
public class Streaming {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
client.chat().completions().createStream(
ChatCompletionRequest.builder()
.model("hinow/hinova")
.addMessage(new Message("user", "Write three lines about the sea at dawn."))
.build(),
chunk -> {
// Note the delta: each chunk carries the new fragment, not the whole answer.
if (!chunk.getChoices().isEmpty()) {
String excerpt = chunk.getChoices().get(0).getDelta().getContent();
if (excerpt != null) {
System.out.print(excerpt);
}
}
});
System.out.println();
}
}It is getDelta(), not getMessage()
In the complete response, the text comes in getMessage().getText(). In streaming, each chunk brings only the new fragment, in getDelta().getContent() — putting it all together is up to you.
Responds instantly, without a job to track, at $0.005 per call. The nine types are constants in ToolsService, and an invalid type is rejected before the request is sent.
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.ToolsService;
public class SearchWeb {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// Answers on the spot: there is no job to follow.
ToolsService.SearchResponse search = client.tools().search(
"best beaches in northeast Brazil", ToolsService.SEARCH, "br", "pt-br");
for (ToolsService.SearchResult item : search.getResults()) {
System.out.println(item.getPosition() + ". " + item.getTitle());
System.out.println(" " + item.getUrl());
}
System.out.printf("%n%d results · US$ %s%n", search.getResults().size(), search.getCost());
}
}| Type | What is filled in for each result |
|---|---|
ToolsService.SEARCH, SCHOLAR, PATENTS | position, title, url, snippet |
ToolsService.NEWS | the above plus source, date, imageUrl |
ToolsService.IMAGES | link, imageUrl, thumbnailUrl, width, height |
ToolsService.VIDEOS | channel, duration, date, thumbnailUrl |
ToolsService.PLACES | address, category, phone, website, rating, coordinates |
ToolsService.SHOPPING | price, delivery, rating, source |
ToolsService.AUTOCOMPLETE | suggestions; results remains empty |
Scans one or more sites for emails, phone numbers, and social profiles, and each finding comes with the exact page where it appeared. Since the scan takes time, this runs as a job.
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.ToolsService;
import java.util.Arrays;
public class SiteContacts {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// The crawl takes a while, so it runs as a job. This method waits for you.
ToolsService.ToolJob job = client.tools().websiteContactsAndWait(
Arrays.asList("https://www.teclia.com"), 2, 10);
String cache = Boolean.TRUE.equals(job.getCached()) ? " (from cache)" : "";
System.out.printf("status: %s · US$ %s%s%n%n", job.getStatus(), job.getCost(), cache);
// Each item carries the type, the value and the exact page it was found on.
for (ToolsService.ContactItem item : job.getResult().getItems()) {
if ("email".equals(item.getType()) || "phone".equals(item.getType())) {
System.out.printf("%-9s%s%n", item.getType(), item.getValue());
System.out.printf("%-9sem %s%n", "", item.getSourceUrl());
} else {
System.out.printf("%-9s%s%n", item.getPlatform(), item.getUrl());
}
}
}
}Running the same crawl twice is not charged again
The job returns with getCached() true when the result came from the cache. If you want to control the lifecycle yourself, use websiteContacts and track it with tools().jobs().retrieve(job.getJobId()) — note that the field is job_id, not id.
Validate what comes back before using it
The scan returns what it found in the page HTML, as it is. Truncated or repeated addresses appear — treat the list as raw material and validate before saving to your database.
Assistants, threads, and runs in the same format as the OpenAI API. You declare the functions, and the model decides when to call them: the run pauses, you execute, and return the result.
import ai.hinow.BetaService;
import ai.hinow.ChatCompletionRequest;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.Tool;
import ai.hinow.ToolCall;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Agent {
private static final Gson GSON = new Gson();
// Your real function: a map here, just for the example.
static String getOrder(String code) {
Map<String, String> order = new LinkedHashMap<>();
if ("A-1001".equals(code)) {
order.put("status", "delivered");
order.put("delivered", "2026-08-02");
} else if ("A-1002".equals(code)) {
order.put("status", "in transit");
order.put("estimated", "2026-08-12");
} else {
order.put("error", "order not found");
}
return GSON.toJson(order);
}
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// The argument schema, in JSON Schema.
Map<String, Object> codeField = new LinkedHashMap<>();
codeField.put("type", "string");
codeField.put("description", "Order code, e.g. A-1001");
Map<String, Object> properties = new LinkedHashMap<>();
properties.put("code", codeField);
Map<String, Object> schema = new LinkedHashMap<>();
schema.put("type", "object");
schema.put("properties", properties);
schema.put("required", Arrays.asList("code"));
// 1. The assistant: model, instructions and the functions it may call.
BetaService.Assistant assistant = client.beta().assistants().create(
BetaService.AssistantRequest.builder()
.model("hinow/himax")
.name("Support")
.instructions("You answer questions about orders. "
+ "Check the tool before stating any status.")
.tools(Arrays.asList(Tool.function(
"get_order",
"Look up an order by its code.",
schema)))
.build());
// 2. The conversation and the question.
BetaService.Thread thread = client.beta().threads().create();
client.beta().threads().messages().create(thread.getId(), "Has order A-1002 arrived?", "user");
// 3. Run it and wait.
BetaService.Run run = client.beta().threads().runs()
.createAndPoll(thread.getId(), assistant.getId());
// 4. While the model asks for functions, run them and hand the result back.
while ("requires_action".equals(run.getStatus())) {
List<BetaService.ToolOutput> outputs = new ArrayList<>();
for (ToolCall call : run.getRequiredAction().getSubmitToolOutputs().getToolCalls()) {
Map<?, ?> callArgs = GSON.fromJson(call.getFunction().getArguments(), Map.class);
String code = String.valueOf(callArgs.get("code"));
System.out.println("-> the model called " + call.getFunction().getName() + "(" + code + ")");
outputs.add(new BetaService.ToolOutput(call.getId(), getOrder(code)));
}
client.beta().threads().runs().submitToolOutputs(thread.getId(), run.getId(), outputs);
run = client.beta().threads().runs().poll(thread.getId(), run.getId());
}
// 5. The final answer is the last message on the thread.
BetaService.ThreadMessageList messages =
client.beta().threads().messages().list(thread.getId(), 1, "desc");
System.out.println("\n" + messages.getData().get(0).getText());
client.beta().assistants().delete(assistant.getId());
}
}requires_action is not an error
It is the run handing control back to you to execute a function. This is why poll returns in this state instead of continuing to spin: read getRequiredAction(), call submitToolOutputs, and resume polling.
Upload files, group them into a base, and search by meaning. Indexing is asynchronous—searching before it finishes returns zero results, without error, which is exactly what poll is for.
import ai.hinow.FilesService;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.RagService;
import ai.hinow.VectorStoresService;
public class Knowledge {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// 1. Upload the document.
FilesService.FileObject file =
client.files().create("returns-policy.txt", "assistants");
System.out.println("file: " + file.getId() + " (" + file.getBytes() + " bytes)");
// 2. Create the store and attach the file to it.
VectorStoresService.VectorStore base = client.vectorStores().create("Support base");
client.vectorStores().files().create(base.getId(), file.getId());
System.out.println("store: " + base.getId());
// 3. Indexing is asynchronous. Searching before it finishes returns nothing
// at all, with no error, so poll waits for it.
VectorStoresService.VectorStoreFile state =
client.vectorStores().files().poll(base.getId(), file.getId());
System.out.println("indexing: " + state.getStatus() + "\n");
// 4. Search by meaning, not by exact word.
RagService.RagSearchResponse hits =
client.rag().search("How many days do I have to return an item?", base.getId(), 3);
for (RagService.RagHit achado : hits.getResults()) {
String excerpt = achado.getText().replace("\n", " ");
excerpt = excerpt.substring(0, Math.min(110, excerpt.length()));
System.out.printf("%.2f %s%n %s…%n", achado.getScore(), achado.getSource(), excerpt);
}
}
}The filter by base is the ragId argument
The API ignores vector_store_id in this endpoint: the search simply scans all documents in the account instead of the base you intended. This is the kind of detail that makes it seem like the search is returning garbage.
Each failure becomes its own class, so you can handle by type instead of comparing message strings. All inherit from HinowException, and those originating from the API carry getStatusCode(), getErrorType(), and the response body.
import ai.hinow.AuthenticationException;
import ai.hinow.ChatCompletionRequest;
import ai.hinow.ConnectionException;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.InvalidRequestException;
import ai.hinow.Message;
import ai.hinow.NotFoundException;
import ai.hinow.RateLimitException;
public class Errors {
public static void main(String[] args) {
try {
Hinow client = new Hinow(null);
client.chat().completions().create(
ChatCompletionRequest.builder()
.model("himax") // the hinow/ prefix is missing
.addMessage(new Message("user", "Olá"))
.build());
} catch (AuthenticationException e) {
System.out.println("Invalid or expired key. Check HINOW_API_KEY.");
} catch (NotFoundException e) {
System.out.println("Not found: " + e.getMessage());
} catch (InvalidRequestException e) {
System.out.println("Invalid request: " + e.getMessage());
} catch (RateLimitException e) {
System.out.println("Rate limited. The SDK already retried; wait a moment.");
} catch (ConnectionException e) {
// Nothing reached the API, so nothing was charged.
System.out.println("Sem answer da API: " + e.getMessage());
} catch (HinowException e) {
// Safety net: any other error the API reported.
System.out.println("Error " + e.getStatusCode() + ": " + e.getMessage());
}
}
}| Class | When it occurs |
|---|---|
AuthenticationException | 401 — missing, invalid, or revoked key |
PermissionException | 403 — the key lacks access to this resource |
NotFoundException | 404 — non-existent model, file, or assistant |
InvalidRequestException | 400 or 422 — a field is missing or a value is out of range |
RateLimitException | 429 — limit reached |
InsufficientBalanceException | 402 — balance exhausted |
ServerException | 5xx — the failure is on the API side |
ConnectionException | the request did not arrive; nothing was charged |
import ai.hinow.BalanceResponse;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.ModelsService;
public class Models {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// Account credit, in US dollars. The response is wrapped in "data".
BalanceResponse balance = client.getBalance();
System.out.printf("balance: US$ %.2f%n%n", balance.getData().getBalance());
// One specific model. The id is namespaced: hinow/himax, not himax.
ModelsService.ModelInfo model = client.models().retrieve("hinow/himax");
System.out.println(model.getName() + " (" + model.getId() + ")");
System.out.println("categories: " + String.join(", ", model.getCategory()));
System.out.println("price per million tokens: input US$ " + model.getCost().getInput()
+ " · output US$ " + model.getCost().getOutput());
}
}| Method | Purpose |
|---|---|
chat().completions() | Chat, streaming, function calling, JSON mode |
embeddings() | Vectors for semantic search |
images() · audio() · video() | Generation |
models() | Catalog, pricing, and features for each model |
tools() | Web search and site contacts |
files() | Document upload |
vectorStores() | Searchable knowledge bases |
rag() | Semantic search in your documents |
beta().assistants() · beta().threads() | Server-side agents |
getBalance() | Account balance |
Hinow client = new Hinow(
System.getenv("HINOW_API_KEY"), // ou null e use a variável
"https://api.hinow.ai", // ou HINOW_BASE_URL
Duration.ofSeconds(120),
2); // repete 429 e 5xxUp to version 1.0.0, the SDK wrapped temperature, maxTokens, topP, and repetitionPenalty inside a parameters object before sending, with the numbers converted to strings. The API accepts this format and ignores it, so these options never took effect: requesting maxTokens(10) returned the full response. Starting from 2.0, everything goes at the root level, as the API expects.
Other changes affecting existing code:
Message.getContentAsString()always returnednullwhen the content was not a pureString. It continues to work, andgetText()is the short name that also combines content from parts.- Errors are now typed, keeping
HinowExceptionas the base class so existingcatchblocks remain valid. responseFormatreceives an object —Map.of("type", "json_object")— which is what the API expects.ModelsServicenow provides name, category, and price, and gained aretrieve()method.
Choosing between HiMax, HiNova, and HiGenesis
What each model does well, how much it costs, and how to write the prompt for each.

