---
title: Java SDK
description: "Java SDK: the ten functions as methods on a client, with not sure thrown as UnsureException."
goal: A Java developer installs the SDK, gets a first answer from decide, learns how not sure and failure arrive, and finds the Java call for each function.
reader: A Java developer who wants answers about text inside a program
kind: reference
verified: 2026-11-03
---

# Java SDK

The Java SDK gives your code the ten functions as methods on a client; each returns a result whose `answer()` you can use directly, such as `true` or `false` from decide.

```java
import dev.thinkthen.*;

public class Example {
    public static void main(String[] args) {
        try (var client = ThinkThen.builder().build()) {
            boolean isRefund = client.decide(
                "Does the customer request a refund?",
                "Please refund my order.").answer();
            System.out.println(isRefund);
        }
    }
}
```

Output

```text
true
```


Add it to Maven or Gradle (Java 22 or later). It includes the native engine:

```text
io.github.botassembly:thinkthen-java:0.2.0
```

With the JAR and its dependencies in a `lib` folder, Java runs the file directly:

```sh
java --enable-native-access=ALL-UNNAMED \
  -cp 'lib/*' Example.java
```

On the module path, use `--enable-native-access=dev.thinkthen`. The client uses the key you set in the [Quickstart](/quickstart/).

## When decide is not sure

Give decide a low and a high bar, and a text that falls between them is not sure. Reading a not-sure answer with `answer()` throws `UnsureException`, so it can't pass for a no. This message lands between the bars:

```java
import dev.thinkthen.*;

public class Unsure {
    public static void main(String[] args) {
        var bars = DecideOptions.builder()
            .threshold(Threshold.band(0.2, 0.8)).build();
        try (var client = ThinkThen.builder().build()) {
            var isRefund = client.decide(
                "Does the customer request a refund?",
                "I was charged twice for one invoice.", bars);
            try {
                if (isRefund.answer()) {
                    System.out.println("Send to refunds.");
                } else {
                    System.out.println("Leave in the inbox.");
                }
            } catch (UnsureException notSure) {
                System.out.println("Not sure. Ask a person.");
            }
        }
    }
}
```

Output

```text
Not sure. Ask a person.
```


Send not sure to a person. To branch without the exception, check `isUnsure()` first. `UnsureException` is a `NoSuchElementException`, and its `probability()` gives the chance of yes.

## When a call fails

Blank text throws `ThinkThenException` at once, and nothing is sent:

```java
import dev.thinkthen.*;

public class Failure {
    public static void main(String[] args) {
        try (var client = ThinkThen.builder().build()) {
            client.decide(
                "Does the customer request a refund?", "");
        } catch (ThinkThenException failure) {
            System.out.println(failure.code().wireName());
        }
    }
}
```

Output

```text
record.empty
```


Any other failure, such as the service being down, comes back as a result whose `isFailed()` is `true`. Its `answer()` throws `ThinkThenException`, whose `code()` names the problem. `throwIfFailed()` throws it sooner. A list passed to a one-message call throws `IllegalArgumentException`; use the `…Stream` call instead. [Troubleshooting](/reference/troubleshooting/) lists each code and its fix.

## The ten functions

Each example asks about the same support-inbox messages as the function pages.

### decide

<details class="example-disclosure">
<summary>decide: yes, no or not sure</summary>

```java
import dev.thinkthen.*;

public class Example {
    public static void main(String[] args) {
        try (var client = ThinkThen.builder().build()) {
            boolean isRefund = client.decide(
                "Does the customer request a refund?",
                "Please refund my order.").answer();
            System.out.println(isRefund);
        }
    }
}
```

Output

```text
true
```


The message asks for a refund, so decide says `true`. `DecideOptions.builder().threshold(Threshold.cut(0.8))` moves the bar, and `Threshold.band(0.2, 0.8)` sets a low and a high bar, as above. `field(Pointer.of("/text"))` picks the field to judge when a message is a `Map` or JSON. `client.decideStream(question, messages)` judges a whole list and returns a `Stream` of results; close it with try-with-resources. [decide](/functions/decide/) shows every answer.

</details>

### choose

<details class="example-disclosure">
<summary>choose: one answer from your list</summary>

```java
import dev.thinkthen.*;
import java.util.List;

public class Choose {
    public static void main(String[] args) {
        try (var client = ThinkThen.builder().build()) {
            String topic = client.choose(
                "What is the message about?",
                "Please refund my order.",
                List.of("billing", "shipping", "other"))
                .answer();
            System.out.println(topic);
        }
    }
}
```

Output

```text
billing
```


A refund request is about billing. With `ChooseOptions.builder().threshold(...)`, choose is not sure when no choice reaches the bar, and `answer()` then throws `UnsureException`. [choose](/functions/choose/) shows every answer.

</details>

### filter

<details class="example-disclosure">
<summary>filter: keep the messages that answer yes</summary>

```java
import dev.thinkthen.*;
import java.util.List;
import java.util.stream.Stream;

public class Filter {
    public static void main(String[] args) {
        List<String> messages = List.of(
            "Please refund my order.",
            "Where is my parcel?",
            "I was charged twice for one invoice.");
        try (var client = ThinkThen.builder().build();
             Stream<String> refunds = client.filter(
                 "Does the customer request a refund?",
                 messages)) {
            refunds.forEach(System.out::println);
        }
    }
}
```

Output

```text
Please refund my order.
I was charged twice for one invoice.
```


filter gives a `Stream` of your own messages that answer yes: the refund request and the double charge. Not-sure messages are left out. If a message fails, the stream first gives every kept message, then throws one `ThinkThenException` whose `failures()` lists each failed result. `client.filterRows(...)` gives a `Filtered` for every message instead, with `item()`, `kept()` and `isUnsure()`:

```java
import dev.thinkthen.*;
import java.util.List;
import java.util.Map;

public class Example {
    public static void main(String[] args) {
        List<Map<String, String>> messages = List.of(
            Map.of("id", "a1", "text", "Please refund my order."),
            Map.of("id", "a2", "text", "Where is my parcel?"),
            Map.of("id", "a3", "text", "I was charged twice for one invoice."));
        var question = "Does the customer request a refund?";
        var options = FilterOptions.builder()
            .field(Pointer.of("/text")).build();
        try (var client = ThinkThen.builder().build();
             var refundChecks =
                 client.filterRows(question, messages, options)) {
            refundChecks.forEach(each -> {
                boolean kept =
                    each.row() instanceof Answered<Boolean, NoDetail> a
                    && a.answer();
                System.out.println(each.item().get("id") + " "
                    + each.row().status() + " kept=" + kept);
            });
        }
    }
}
```

Output

```text
a1 ANSWERED kept=true
a2 ANSWERED kept=false
a3 ANSWERED kept=true
```


[filter](/functions/filter/) shows every answer.

</details>

### tag

<details class="example-disclosure">
<summary>tag: every label that fits</summary>

```java
import dev.thinkthen.*;
import java.util.List;

public class Tag {
    public static void main(String[] args) {
        try (var client = ThinkThen.builder().build()) {
            List<String> topics = client.tag(
                "Which topics appear?",
                "I was charged twice and my parcel is late again.",
                List.of("billing", "shipping")).answer();
            System.out.println(topics);
        }
    }
}
```

Output

```text
[billing, shipping]
```


The message names a double charge and a late parcel, so both labels fit. The list is empty when none fits. [tag](/functions/tag/) shows every answer.

</details>

### score

<details class="example-disclosure">
<summary>score: a number from 0 to 1</summary>

```java
import dev.thinkthen.*;
import java.util.List;

public class Score {
    public static void main(String[] args) {
        try (var client = ThinkThen.builder().build()) {
            double urgency = client.score(
                "How urgent is the request?",
                "Please refund my order.",
                List.of("routine", "urgent")).answer();
            System.out.println(urgency);
        }
    }
}
```

Output

```text
0.05
```


A refund request is routine, so it scores near 0. Levels run lowest first. [score](/functions/score/) shows every answer.

</details>

### rank

<details class="example-disclosure">
<summary>rank: put messages in order</summary>

```java
import dev.thinkthen.*;
import java.util.List;
import java.util.stream.Stream;

public class Rank {
    public static void main(String[] args) {
        List<String> messages = List.of(
            "Please refund my order.",
            "Where is my parcel?",
            "I was charged twice for one invoice.");
        try (var client = ThinkThen.builder().build();
             Stream<Ranked<JsonValue>> byUrgency = client.rank(
                 "How urgent is the request?", messages,
                 List.of("routine", "urgent"))) {
            byUrgency.forEach(ranked -> {
                long rank = ranked.answer();
                var text = ((JsonString) ranked.item()).value();
                System.out.println(rank + " " + text);
            });
        }
    }
}
```

Output

```text
1 I was charged twice for one invoice.
2 Where is my parcel?
3 Please refund my order.
```


The double charge is the most urgent, so it ranks 1. `answer()` is the rank, a `Long`. `item()` is a `JsonValue` copy of the message, so text comes back as a `JsonString`. `position()` is the message's place in your list, counted from 1. `RankOptions.builder().top(2)` ranks only the first two. [rank](/functions/rank/) shows every answer.

</details>

### find

<details class="example-disclosure">
<summary>find: the line that answers</summary>

```java
import dev.thinkthen.*;
import java.util.Optional;

public class Find {
    public static void main(String[] args) {
        String message = "Morgan works for Example Group.\n"
            + "Please refund my order.";
        try (var client = ThinkThen.builder().build()) {
            Optional<Span> action = client.find(
                "Which line states the requested action?",
                message).answer();
            System.out.println(
                action.map(Span::text).orElse("no line answers"));
        }
    }
}
```

Output

```text
Please refund my order.
```


The second line holds the request. The answer is an `Optional<Span>`, empty when no line answers; a `Span` has `text()`, `start()` and `end()`, counted in characters. [find](/functions/find/) shows every answer.

</details>

### recognize

<details class="example-disclosure">
<summary>recognize: names and their kinds</summary>

```java
import dev.thinkthen.*;
import java.util.List;

public class Recognize {
    public static void main(String[] args) {
        try (var client = ThinkThen.builder().build()) {
            List<Entity> names = client.recognize(
                "Morgan works for Example Group.",
                List.of("person", "organization")).answer();
            for (Entity name : names) {
                System.out.println(name.mention().kind() + " "
                    + name.mention().span().text());
            }
        }
    }
}
```

Output

```text
person Morgan
organization Example Group
```


Morgan is a person and Example Group an organization. Each `Entity` has a `mention()`, with its `kind()` and `span()`, and a `probability()`. [recognize](/functions/recognize/) shows every answer.

</details>

### relate

<details class="example-disclosure">
<summary>relate: how names connect</summary>

```java
import dev.thinkthen.*;
import java.util.List;

public class Relate {
    public static void main(String[] args) {
        String message = "Morgan works for Example Group.\n"
            + "Please refund my order.";
        var worksFor = RelationRule.of(
            "person", "organization", "works_for");
        try (var client = ThinkThen.builder().build()) {
            List<Relation> relations = client.relate(
                message, List.of(worksFor)).answer();
            for (Relation relation : relations) {
                System.out.println(
                    relation.source().span().text() + " "
                    + relation.relation() + " "
                    + relation.target().span().text());
            }
        }
    }
}
```

Output

```text
Morgan works_for Example Group
```


Morgan works for Example Group, so relate links the two by the rule. relate finds the names itself, so it needs no recognize call first. `RelationRule.builder(source, target, relation)` adds `reads(...)` to word the relation and `either(true)` to make it work both ways. [relate](/functions/relate/) shows every answer.

</details>

### annotate

<details class="example-disclosure">
<summary>annotate: several questions at once</summary>

```java
import dev.thinkthen.*;
import java.util.List;

public class Annotate {
    public static void main(String[] args) {
        List<String> messages = List.of(
            "Please refund my order.",
            "Where is my parcel?",
            "I was charged twice for one invoice.");
        var questions = AnnotationQuestions.builder()
            .put("refund", DecideQuestion.of(
                "Does the customer request a refund?"))
            .put("topic", ChooseQuestion.of(
                "What is the message about?",
                List.of("billing", "shipping", "other")))
            .put("urgency", ScoreQuestion.of(
                "How urgent is the request?",
                List.of("routine", "urgent")))
            .build();
        try (var client = ThinkThen.builder().build()) {
            for (String message : messages) {
                Annotations form =
                    client.annotate(questions, message).answer();
                System.out.println(
                    form.decide("refund").answer() + " "
                    + form.choose("topic").answer() + " "
                    + form.score("urgency").answer());
            }
        }
    }
}
```

Output

```text
true billing 0.05
false shipping 0.14
true billing 0.45
```


Each message gets all three answers: refund or not, the topic, and its urgency. Each question's `answer()` throws on not sure or failure, as decide's does. `form.decide("refund")` throws `ThinkThenException` with the code `usage.argument` for a name you never added or a question of another function. [annotate](/functions/annotate/) shows every answer.

</details>

## The full API

`client.async()` gives every one-message call as a `CompletableFuture`. The [Java API reference](https://github.com/botassembly/thinkthen/blob/v0.2.0/sdk/java/API.md) lists every name, and its Javadoc ships in the sources JAR.
