Skip to content

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.

Installation

The library is published via JitPack, so the repository must be declared alongside the dependency. The dependency alone is not enough.

pom.xmlxml
<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>

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.

terminalbash
export HINOW_API_KEY="hi_sua_chave_aqui"

The first call

PrimeiraChamada.javajava
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.

Showing the response as it arrives

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.

Streaming.javajava
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.

BuscaWeb.javajava
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());
    }
}
TypeWhat is filled in for each result
ToolsService.SEARCH, SCHOLAR, PATENTSposition, title, url, snippet
ToolsService.NEWSthe above plus source, date, imageUrl
ToolsService.IMAGESlink, imageUrl, thumbnailUrl, width, height
ToolsService.VIDEOSchannel, duration, date, thumbnailUrl
ToolsService.PLACESaddress, category, phone, website, rating, coordinates
ToolsService.SHOPPINGprice, delivery, rating, source
ToolsService.AUTOCOMPLETEsuggestions; results remains empty

Website contacts

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.

ContatosSite.javajava
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.

Agents

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.

Agente.javajava
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.

Conhecimento.javajava
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.

Errors

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.

Erros.javajava
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());
        }
    }
}
ClassWhen it occurs
AuthenticationException401 — missing, invalid, or revoked key
PermissionException403 — the key lacks access to this resource
NotFoundException404 — non-existent model, file, or assistant
InvalidRequestException400 or 422 — a field is missing or a value is out of range
RateLimitException429 — limit reached
InsufficientBalanceException402 — balance exhausted
ServerException5xx — the failure is on the API side
ConnectionExceptionthe request did not arrive; nothing was charged

Balance and catalog

Modelos.javajava
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());
    }
}

Everything the client exposes

MethodPurpose
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

Configuration

Cliente.javajava
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 5xx

Coming from version 1.x

Up 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 returned null when the content was not a pure String. It continues to work, and getText() is the short name that also combines content from parts.
  • Errors are now typed, keeping HinowException as the base class so existing catch blocks remain valid.
  • responseFormat receives an object — Map.of("type", "json_object") — which is what the API expects.
  • ModelsService now provides name, category, and price, and gained a retrieve() method.

Choosing between HiMax, HiNova, and HiGenesis

What each model does well, how much it costs, and how to write the prompt for each.

Was this page helpful?