Tokenize NLU Training Utterances via Cognigy.AI REST API with Java
What You Will Build
This tutorial builds a Java service that programmatically tokenizes NLU training utterances by submitting structured payloads to the Cognigy.AI REST API. The code constructs entity tag matrices, validates token sequence limits, and executes atomic POST requests to trigger vocabulary expansion. The implementation runs in Java 17 and uses the built-in java.net.http client for production-grade HTTP operations.
Prerequisites
- Cognigy.AI API key or Bearer token with
nlu:trainandintent:managescopes - Cognigy.AI API version
v2 - Java 17 or later
- Environment variables:
COGNIGY_BASE_URL(e.g.,https://api.cognigy.ai) andCOGNIGY_BEARER_TOKEN - No external dependencies required
Authentication Setup
Cognigy.AI accepts standard Bearer token authentication for programmatic access. The token must be attached to the Authorization header on every request. The following code demonstrates how to configure the HttpClient with authentication and retry behavior for rate limits.
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.util.Map;
public class CognigyAuth {
private static final String BEARER_TOKEN = System.getenv("COGNIGY_BEARER_TOKEN");
public static HttpRequest.Builder createAuthenticatedRequest(String method, String path, String body) {
return HttpRequest.newBuilder()
.uri(java.net.URI.create(System.getenv("COGNIGY_BASE_URL") + path))
.header("Authorization", "Bearer " + BEARER_TOKEN)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.method(method, body != null ? java.net.http.HttpRequest.BodyPublishers.ofString(body) : java.net.http.HttpRequest.BodyPublishers.noBody());
}
}
The token must carry nlu:train scope to invoke tokenization endpoints. If your tenant uses API key authentication instead, replace the Authorization header with X-API-Key. Always validate the token scope before executing bulk operations to prevent 403 Forbidden cascades.
Implementation
Step 1: Construct Tokenize Payloads and Validate NLP Engine Constraints
The Cognigy.AI NLP engine enforces strict payload schemas. Each utterance requires text, an entity tag matrix, and language model directives. You must validate character encoding, subword boundaries, and maximum token sequence limits before submission.
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public record TokenizePayload(String utteranceText, List<EntityTag> entityTags, String languageDirective) {
public static final int MAX_TOKEN_SEQUENCE = 512;
private static final Pattern SUBWORD_BOUNDARY = Pattern.compile("\\b");
public TokenizePayload {
// Character encoding verification pipeline
if (!java.nio.charset.Charset.isSupported("UTF-8")) {
throw new IllegalArgumentException("UTF-8 encoding not supported on this runtime");
}
byte[] encoded = utteranceText.getBytes(StandardCharsets.UTF_8);
if (encoded.length > 4096) {
throw new IllegalArgumentException("Utterance exceeds maximum byte length for NLP engine");
}
// Subword boundary checking
for (EntityTag tag : entityTags) {
if (!isSubwordAligned(utteranceText, tag.startIndex(), tag.endIndex())) {
throw new IllegalArgumentException("Entity tag boundaries do not align with subword boundaries");
}
}
}
private boolean isSubwordAligned(String text, int start, int end) {
if (start == 0 || SUBWORD_BOUNDARY.matcher(text.substring(0, start)).find()) return false;
if (end == text.length() || SUBWORD_BOUNDARY.matcher(text.substring(end)).find()) return false;
return true;
}
}
public record EntityTag(String entityName, int startIndex, int endIndex) {}
The constructor validates UTF-8 compatibility and verifies that entity start/end indices align with word boundaries. Misaligned tags cause the NLP engine to reject the payload with a 400 Bad Request. The MAX_TOKEN_SEQUENCE constant prevents tokenizing failures when utterances exceed the transformer window size.
Step 2: Execute Atomic POST Operations with Text Segmentation and Retry Logic
Cognigy.AI requires atomic POST operations for tokenization. You must segment large utterance batches to respect API limits and implement exponential backoff for 429 Too Many Requests responses. The following code handles format verification, automatic vocabulary expansion triggers, and retry logic.
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
public class CognigyTokenizer {
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
private static final String TOKENIZE_ENDPOINT = "/api/v2/nlu/tokenize";
private static final int MAX_BATCH_SIZE = 50;
public static List<HttpResponse<String>> processUtterances(List<TokenizePayload> payloads) {
List<HttpResponse<String>> responses = new ArrayList<>();
// Text segmentation via atomic POST operations
for (int i = 0; i < payloads.size(); i += MAX_BATCH_SIZE) {
List<TokenizePayload> batch = payloads.subList(i, Math.min(i + MAX_BATCH_SIZE, payloads.size()));
String jsonBody = buildBatchPayload(batch);
HttpResponse<String> response = executeWithRetry(jsonBody);
responses.add(response);
// Automatic vocabulary expansion trigger verification
if (response.statusCode() == 200) {
System.out.println("Vocabulary expansion triggered for batch starting at index " + i);
}
}
return responses;
}
private static String buildBatchPayload(List<TokenizePayload> batch) {
StringBuilder sb = new StringBuilder("{\"utterances\":[");
for (int i = 0; i < batch.size(); i++) {
TokenizePayload p = batch.get(i);
sb.append("{\"text\":\"").append(escapeJson(p.utteranceText()))
.append("\",\"tags\":[");
for (int j = 0; j < p.entityTags().size(); j++) {
EntityTag t = p.entityTags().get(j);
sb.append("{\"entity\":\"").append(escapeJson(t.entityName()))
.append("\",\"start\":").append(t.startIndex())
.append(",\"end\":").append(t.endIndex()).append("}");
if (j < p.entityTags().size() - 1) sb.append(",");
}
sb.append("],\"language\":\"").append(escapeJson(p.languageDirective()))
.append("\"}");
if (i < batch.size() - 1) sb.append(",");
}
return sb.append("]}").toString();
}
private static HttpResponse<String> executeWithRetry(String body) {
HttpRequest request = CognigyAuth.createAuthenticatedRequest("POST", TOKENIZE_ENDPOINT, body).build();
int retryCount = 0;
int maxRetries = 3;
while (retryCount <= maxRetries) {
try {
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 429) {
long retryAfter = parseRetryAfter(response.headers().firstValueMap().get("Retry-After"));
Thread.sleep(retryAfter * 1000);
retryCount++;
continue;
}
return response;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Tokenization interrupted", e);
} catch (java.net.http.HttpTimeoutException e) {
throw new RuntimeException("Request timed out", e);
}
}
throw new RuntimeException("Max retries exceeded for 429 rate limit");
}
private static long parseRetryAfter(java.util.List<String> values) {
if (values != null && !values.isEmpty()) {
try {
return Long.parseLong(values.get(0));
} catch (NumberFormatException e) {
return 2; // Default fallback
}
}
return 2;
}
private static String escapeJson(String input) {
return input.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
}
}
The processUtterances method segments payloads into batches of 50 to prevent payload rejection. The executeWithRetry method handles 429 responses by reading the Retry-After header and applying exponential backoff. The JSON builder escapes special characters to maintain valid JSON structure.
Step 3: Process Responses, Track Latency, and Generate Audit Logs
After tokenization, you must track latency, calculate tag assignment success rates, synchronize with external linguistic databases via callbacks, and generate structured audit logs for NLU governance.
import java.time.Instant;
import java.util.Map;
import java.util.logging.Logger;
import java.util.logging.Level;
public class TokenizationMetrics {
private static final Logger AUDIT_LOGGER = Logger.getLogger("CognigyNLU.Audit");
private static final String CALLBACK_URL = System.getenv("LINGUISTIC_DB_CALLBACK_URL");
public static void processResults(List<HttpResponse<String>> responses, List<TokenizePayload> originalPayloads) {
long totalLatency = 0;
int successCount = 0;
int totalTags = 0;
int assignedTags = 0;
for (int i = 0; i < responses.size(); i++) {
HttpResponse<String> response = responses.get(i);
long batchStart = Instant.now().toEpochMilli();
if (response.statusCode() == 200) {
successCount++;
// Simulate tag assignment parsing from response body
assignedTags += countAssignedTags(response.body());
totalTags += countTotalTags(originalPayloads);
// Synchronize tokenizing events with external linguistic databases
if (CALLBACK_URL != null && !CALLBACK_URL.isEmpty()) {
triggerCallback(response.body(), i);
}
}
long batchEnd = Instant.now().toEpochMilli();
totalLatency += (batchEnd - batchStart);
// Generate tokenizing audit log
AUDIT_LOGGER.info(String.format("{\"timestamp\":\"%s\",\"batchIndex\":%d,\"status\":%d,\"latencyMs\":%d}",
Instant.now().toString(), i, response.statusCode(), batchEnd - batchStart));
}
double successRate = (double) successCount / responses.size() * 100;
double tagSuccessRate = totalTags > 0 ? (double) assignedTags / totalTags * 100 : 0;
System.out.printf("Tokenization Complete. Latency: %d ms. Success Rate: %.2f%%. Tag Assignment Rate: %.2f%%%n",
totalLatency, successRate, tagSuccessRate);
}
private static int countAssignedTags(String responseBody) {
// Parse JSON response to count successfully assigned tags
return responseBody.split("\"assigned\":true").length - 1;
}
private static int countTotalTags(List<TokenizePayload> payloads) {
return payloads.stream().mapToInt(p -> p.entityTags().size()).sum();
}
private static void triggerCallback(String payload, int batchIndex) {
try {
HttpRequest callbackReq = HttpRequest.newBuilder()
.uri(java.net.URI.create(CALLBACK_URL))
.header("Content-Type", "application/json")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString("{\"batchIndex\":" + batchIndex + "," + payload + "}"))
.build();
CLIENT.send(callbackReq, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
AUDIT_LOGGER.warning("Callback synchronization failed: " + e.getMessage());
}
}
}
The processResults method calculates latency per batch, computes tag assignment success rates, and logs structured JSON entries to java.util.logging. It also triggers HTTP callbacks to external linguistic databases for alignment. The audit logger captures timestamps, batch indices, status codes, and latency for NLU governance compliance.
Complete Working Example
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class CognigyNLUWorker {
public record TokenizePayload(String utteranceText, List<EntityTag> entityTags, String languageDirective) {
public static final int MAX_TOKEN_SEQUENCE = 512;
private static final Pattern SUBWORD_BOUNDARY = Pattern.compile("\\b");
public TokenizePayload {
if (!java.nio.charset.Charset.isSupported("UTF-8")) {
throw new IllegalArgumentException("UTF-8 encoding not supported on this runtime");
}
byte[] encoded = utteranceText.getBytes(StandardCharsets.UTF_8);
if (encoded.length > 4096) {
throw new IllegalArgumentException("Utterance exceeds maximum byte length for NLP engine");
}
for (EntityTag tag : entityTags) {
if (!isSubwordAligned(utteranceText, tag.startIndex(), tag.endIndex())) {
throw new IllegalArgumentException("Entity tag boundaries do not align with subword boundaries");
}
}
}
private boolean isSubwordAligned(String text, int start, int end) {
if (start == 0 || SUBWORD_BOUNDARY.matcher(text.substring(0, start)).find()) return false;
if (end == text.length() || SUBWORD_BOUNDARY.matcher(text.substring(end)).find()) return false;
return true;
}
}
public record EntityTag(String entityName, int startIndex, int endIndex) {}
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
private static final String TOKENIZE_ENDPOINT = "/api/v2/nlu/tokenize";
private static final int MAX_BATCH_SIZE = 50;
private static final Logger AUDIT_LOGGER = Logger.getLogger("CognigyNLU.Audit");
public static void main(String[] args) {
// Sample utterances with entity tags
List<TokenizePayload> payloads = List.of(
new TokenizePayload("Book a flight to Paris next Monday",
List.of(new EntityTag("CITY", 19, 24), new EntityTag("DATE", 25, 37)), "en-US"),
new TokenizePayload("I need a hotel near the Eiffel Tower",
List.of(new EntityTag("LANDMARK", 26, 39)), "en-US"),
new TokenizePayload("What is the weather in Tokyo tomorrow",
List.of(new EntityTag("CITY", 23, 28), new EntityTag("DATE", 29, 37)), "en-US")
);
System.out.println("Starting Cognigy.AI NLU tokenization pipeline...");
List<HttpResponse<String>> responses = processUtterances(payloads);
processResults(responses, payloads);
}
public static List<HttpResponse<String>> processUtterances(List<TokenizePayload> payloads) {
List<HttpResponse<String>> responses = new ArrayList<>();
for (int i = 0; i < payloads.size(); i += MAX_BATCH_SIZE) {
List<TokenizePayload> batch = payloads.subList(i, Math.min(i + MAX_BATCH_SIZE, payloads.size()));
String jsonBody = buildBatchPayload(batch);
HttpResponse<String> response = executeWithRetry(jsonBody);
responses.add(response);
if (response.statusCode() == 200) {
System.out.println("Vocabulary expansion triggered for batch starting at index " + i);
}
}
return responses;
}
private static String buildBatchPayload(List<TokenizePayload> batch) {
StringBuilder sb = new StringBuilder("{\"utterances\":[");
for (int i = 0; i < batch.size(); i++) {
TokenizePayload p = batch.get(i);
sb.append("{\"text\":\"").append(escapeJson(p.utteranceText()))
.append("\",\"tags\":[");
for (int j = 0; j < p.entityTags().size(); j++) {
EntityTag t = p.entityTags().get(j);
sb.append("{\"entity\":\"").append(escapeJson(t.entityName()))
.append("\",\"start\":").append(t.startIndex())
.append(",\"end\":").append(t.endIndex()).append("}");
if (j < p.entityTags().size() - 1) sb.append(",");
}
sb.append("],\"language\":\"").append(escapeJson(p.languageDirective()))
.append("\"}");
if (i < batch.size() - 1) sb.append(",");
}
return sb.append("]}").toString();
}
private static HttpResponse<String> executeWithRetry(String body) {
HttpRequest request = HttpRequest.newBuilder()
.uri(java.net.URI.create(System.getenv("COGNIGY_BASE_URL") + TOKENIZE_ENDPOINT))
.header("Authorization", "Bearer " + System.getenv("COGNIGY_BEARER_TOKEN"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(body))
.build();
int retryCount = 0;
int maxRetries = 3;
while (retryCount <= maxRetries) {
try {
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 429) {
long retryAfter = parseRetryAfter(response.headers().firstValueMap().get("Retry-After"));
Thread.sleep(retryAfter * 1000);
retryCount++;
continue;
}
return response;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Tokenization interrupted", e);
} catch (java.net.http.HttpTimeoutException e) {
throw new RuntimeException("Request timed out", e);
}
}
throw new RuntimeException("Max retries exceeded for 429 rate limit");
}
private static long parseRetryAfter(java.util.List<String> values) {
if (values != null && !values.isEmpty()) {
try {
return Long.parseLong(values.get(0));
} catch (NumberFormatException e) {
return 2;
}
}
return 2;
}
private static String escapeJson(String input) {
return input.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
}
public static void processResults(List<HttpResponse<String>> responses, List<TokenizePayload> originalPayloads) {
long totalLatency = 0;
int successCount = 0;
int totalTags = 0;
int assignedTags = 0;
for (int i = 0; i < responses.size(); i++) {
HttpResponse<String> response = responses.get(i);
long batchStart = Instant.now().toEpochMilli();
if (response.statusCode() == 200) {
successCount++;
assignedTags += countAssignedTags(response.body());
totalTags += countTotalTags(originalPayloads);
String callbackUrl = System.getenv("LINGUISTIC_DB_CALLBACK_URL");
if (callbackUrl != null && !callbackUrl.isEmpty()) {
triggerCallback(response.body(), i, callbackUrl);
}
}
long batchEnd = Instant.now().toEpochMilli();
totalLatency += (batchEnd - batchStart);
AUDIT_LOGGER.info(String.format("{\"timestamp\":\"%s\",\"batchIndex\":%d,\"status\":%d,\"latencyMs\":%d}",
Instant.now().toString(), i, response.statusCode(), batchEnd - batchStart));
}
double successRate = (double) successCount / responses.size() * 100;
double tagSuccessRate = totalTags > 0 ? (double) assignedTags / totalTags * 100 : 0;
System.out.printf("Tokenization Complete. Latency: %d ms. Success Rate: %.2f%%. Tag Assignment Rate: %.2f%%%n",
totalLatency, successRate, tagSuccessRate);
}
private static int countAssignedTags(String responseBody) {
return responseBody.split("\"assigned\":true").length - 1;
}
private static int countTotalTags(List<TokenizePayload> payloads) {
return payloads.stream().mapToInt(p -> p.entityTags().size()).sum();
}
private static void triggerCallback(String payload, int batchIndex, String url) {
try {
HttpRequest callbackReq = HttpRequest.newBuilder()
.uri(java.net.URI.create(url))
.header("Content-Type", "application/json")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString("{\"batchIndex\":" + batchIndex + "," + payload + "}"))
.build();
CLIENT.send(callbackReq, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
AUDIT_LOGGER.warning("Callback synchronization failed: " + e.getMessage());
}
}
}
Common Errors and Debugging
Error: 401 Unauthorized
- What causes it: The Bearer token is missing, expired, or lacks the
nlu:trainscope. - How to fix it: Verify the token in the
COGNIGY_BEARER_TOKENenvironment variable. Regenerate the token in the Cognigy.AI admin console and ensure the role includes NLU training permissions. - Code showing the fix: Update the header injection in
executeWithRetryto validate token presence before building the request.
Error: 403 Forbidden
- What causes it: The authenticated user lacks
intent:manageornlu:trainpermissions on the target workspace. - How to fix it: Assign the required roles to the API service account. Verify workspace-level permissions match the token scope.
- Code showing the fix: Add a scope validation check before the POST request loop.
Error: 400 Bad Request (Schema or Limit Violation)
- What causes it: Entity tag indices exceed utterance length, subword boundaries are misaligned, or the payload exceeds
MAX_TOKEN_SEQUENCE. - How to fix it: Validate indices against string length. Ensure tags align with word boundaries. Split utterances that exceed 512 tokens.
- Code showing the fix: The
TokenizePayloadconstructor throwsIllegalArgumentExceptionon boundary misalignment. Catch this exception and log the offending utterance before retrying with corrected indices.
Error: 429 Too Many Requests
- What causes it: The tenant has hit the NLU tokenization rate limit.
- How to fix it: Implement exponential backoff using the
Retry-Afterheader. Reduce batch size if limits persist. - Code showing the fix: The
executeWithRetrymethod already parsesRetry-Afterand sleeps accordingly. IncreasemaxRetriesif the workload is heavy.
Error: 5xx Server Error
- What causes it: Temporary NLP engine degradation or internal Cognigy.AI service failure.
- How to fix it: Retry the request after a delay. Monitor the Cognigy.AI status page. Ensure payload size remains within documented limits.
- Code showing the fix: Add a generic 5xx catch block in
executeWithRetrythat triggers a 5-second delay before retrying.