Skip to content

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.

Example.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

true

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

Terminal window
io.github.botassembly:thinkthen-java:0.2.0

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

Terminal window
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.

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:

Unsure.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

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.

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

Failure.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

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 lists each code and its fix.

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

decide: yes, no or not sure
Example.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

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 shows every answer.

choose: one answer from your list
Choose.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

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 shows every answer.

filter: keep the messages that answer yes
Filter.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

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():

Example.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

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

filter shows every answer.

tag: every label that fits
Tag.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

[billing, shipping]

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

score: a number from 0 to 1
Score.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

0.05

A refund request is routine, so it scores near 0. Levels run lowest first. score shows every answer.

rank: put messages in order
Rank.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

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 shows every answer.

find: the line that answers
Find.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

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 shows every answer.

recognize: names and their kinds
Recognize.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

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 shows every answer.

relate: how names connect
Relate.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

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 shows every answer.

annotate: several questions at once
Annotate.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

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 shows every answer.

client.async() gives every one-message call as a CompletableFuture. The Java API reference lists every name, and its Javadoc ships in the sources JAR.