A Java SDK for interacting with Hyland CIC services, providing modular HTTP clients, extensible serialization/mapping, and high-level APIs for Ingest, Agent, QnA, Nucleus, Knowledge Enrichment and Data Curation operations.
flowchart TD
subgraph cic-http-client
A1(AbstractHttpClient)
A2(AbstractHttpClientBuilder)
A3(AbstractAuthenticatedHttpClient)
A4(AbstractAuthenticatedHttpClientBuilder)
R1(RetryPolicy)
R2(BackoffStrategy)
R3(RetryCondition)
B1(MapperService)
B2(SerializerFactory)
B3(MapperFactory)
A1 --> A3
A2 --> A4
A1 -.- R1
R1 --- R2
R1 --- R3
B1 --- B2
B1 --- B3
A1 -.- B1
end
subgraph cic-http-client-jackson2
B4(Jackson2SerializerFactory)
B2 --- B4
end
subgraph cic-ingest
C1(IngestHttpClient)
C2(IngestService)
D1(IngestEventMapper)
D2(PreSignedUrlMapper)
A3 ---> C1
A4 -..-> C1
C1 --> C2
B3 --- D1
B3 --- D2
end
subgraph cic-ke
E1(KEHttpClient)
E2(KEService)
E3(DataCurationHttpClient)
E4(DataCurationService)
E5(KEMapperFactory)
A3 ---> E1
A3 ---> E3
A4 -..-> E1
A4 -..-> E3
E1 --> E2
E3 --> E4
B3 --- E5
end
- Package:
org.hyland.sdk.cic.http.client.base - Provides abstract classes for building HTTP clients:
AbstractHttpClientandAbstractHttpClientBuilder: Base for all HTTP clients.AbstractAuthenticatedHttpClientand builder: Extend these to create new modules (e.g., cic-knowledge-discovery, cic-knowledge-enrichment) that require authentication.
- How to extend:
- Create a new client class extending
AbstractAuthenticatedHttpClient. - Implement a builder extending
AbstractAuthenticatedHttpClientBuilder.
- Create a new client class extending
Example:
public class KnowledgeDiscoveryHttpClient extends AbstractAuthenticatedHttpClient {
public KnowledgeDiscoveryHttpClient(Builder builder) {
super(builder);
}
// ...custom methods...
public static class Builder extends AbstractAuthenticatedHttpClientBuilder<Builder, KnowledgeDiscoveryHttpClient> {
public Builder(String baseUrl, AuthenticationHttpClient.Builder authBuilder) {
super(baseUrl, authBuilder);
}
@Override
public KnowledgeDiscoveryHttpClient build() {
return new KnowledgeDiscoveryHttpClient(this);
}
}
}- Package:
org.hyland.sdk.cic.http.client.retry - Pluggable retry mechanism built into
AbstractHttpClient. All HTTP client modules (cic-ingest, cic-agent, cic-ke, future modules) inherit retry support automatically. - Key classes:
RetryPolicy: Configures retry behavior — max attempts, backoff strategy, and retry condition. Created via builder or static factories.BackoffStrategy: Functional interface for computing delay between retries. Built-in:fixedDelay(Duration)andexponentialDelay(Duration baseDelay, Duration maxDelay)(full jitter).RetryCondition: Functional interface determining whether a failed request should be retried. Composable viaand()/or(). Built-in:defaultCondition()(retries idempotent methods on 408, 429, 500, 502, 503, 504, andIOException; non-idempotent methods are never retried) andnone().RetryContext: Immutable context passed to conditions and strategies, containing attempt number, HTTP method, HTTP status code, and exception.
Default behavior:
When no retry policy is explicitly configured, RetryPolicy.defaultPolicy() is applied automatically:
- Max attempts: 3 (1 initial + 2 retries)
- Backoff: exponential with full jitter (100ms base delay, 20s max delay)
- Condition: retries idempotent methods on server errors (5xx), rate limiting (429), timeouts (408), and
IOException; non-idempotent methods (POST, PATCH) are never retried
Example: Default policy (applied automatically)
IngestHttpClient client = IngestHttpClient.from("https://api.example.com", authBuilder)
.sourceId("my-source-id")
.hxpEnvironment("my-hxp-environment")
.build(); // default retry policy is appliedExample: Custom retry policy
RetryPolicy policy = RetryPolicy.builder()
.maxAttempts(5)
.backoffStrategy(BackoffStrategy.fixedDelay(Duration.ofSeconds(2)))
.retryCondition(context -> context.statusCode() == 503)
.build();
IngestHttpClient client = IngestHttpClient.from("https://api.example.com", authBuilder)
.sourceId("my-source-id")
.hxpEnvironment("my-hxp-environment")
.retryPolicy(policy)
.build();Example: Disabling retries
IngestHttpClient client = IngestHttpClient.from("https://api.example.com", authBuilder)
.sourceId("my-source-id")
.hxpEnvironment("my-hxp-environment")
.retryPolicy(RetryPolicy.none())
.build();- Package:
org.hyland.sdk.cic.http.client.mapper - Centralized serialization and mapping via
MapperService:- Uses Java SPI (
ServiceLoader) to discover implementations ofSerializerFactory(for marshallers like Jackson2) andMapperFactory(for business object mappers). - To contribute a new marshaller, implement
SerializerFactoryand register inMETA-INF/services. - To contribute a new mapper, implement
MapperFactoryfor your business object (e.g.,IngestEvent) and register it.
- Uses Java SPI (
Example: Registering a MapperFactory
public class IngestEventMapperFactory implements MapperService.MapperFactory {
@Override
public <T> CICMapper<T> getMapper(Class<T> type) {
if (type == IngestEvent.class) {
return (CICMapper<T>) new IngestEventMapper();
}
return null;
}
}Register the factory in META-INF/services/org.hyland.sdk.cic.http.client.mapper.MapperService$MapperFactory.
Example: Registering a SerializerFactory (Jackson2)
public class Jackson2SerializerFactory implements MapperService.SerializerFactory {
@Override
public CICSerializer getSerializer() {
return new Jackson2Serializer();
}
}Register the factory in META-INF/services/org.hyland.sdk.cic.http.client.mapper.MapperService$SerializerFactory.
See the cic-http-client-jackson2 module for a full implementation.
Best Practice:
- Keep mappers and serializers stateless and thread-safe.
- Use SPI for easy extension and modularity.
CICBlob:
- Package:
org.hyland.sdk.cic.http.client.mapper.object - Represents a binary payload (content, digest, and optional content type/name/size) exchanged with CIC, e.g. when uploading a document via
IngestService. getInputStream()is expected to return a newInputStreamon every call, so the sameCICBlobinstance can be safely re-read (e.g. across upload retries).- Build instances with
CICBlob.builder(...)instead of writing an anonymous class — handy in tests and call sites alike:CICBlob.builder(String content)— content encoded as UTF-8.CICBlob.builder(byte[] content)— content is already available in memory.CICBlob.builder(Supplier<InputStream> inputStreamSupplier)— for larger/streamed content (e.g. aFile). The supplier is invoked each timegetInputStream()is called, so it must produce a fresh stream every time rather than reusing an already-consumed one.CICBlob.builder(Path path)— for a file-backed blob: content is streamed from disk on everygetInputStream()call, andname/sizeare pre-filled from the file when available (still overridable).
- Performance note: the
Builderis primarily meant for convenience (e.g. tests, small/one-off blobs). Integrations dealing with large files should implementCICBlobdirectly instead, so the content is only ever streamed from its source (e.g. disk) and never fully loaded into memory.
Example: Building a CICBlob
CICBlob textBlob = CICBlob.builder("Hello, world!").contentType("text/plain").build();
byte[] content = Files.readAllBytes(path);
CICBlob fileBlob = CICBlob.builder(content)
.contentType("application/pdf")
.name("invoice.pdf")
.digest("sha256:abc123")
.build();
CICBlob streamedBlob = CICBlob.builder(Paths.get("invoice.pdf")).contentType("application/pdf").build();Example: Implementing CICBlob to stream a file without loading it into memory
public record FileCICBlob(Path path, String contentType, String digest) implements CICBlob {
@Override
public InputStream getInputStream() {
try {
return Files.newInputStream(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public Optional<String> getDigest() {
return Optional.ofNullable(digest);
}
@Override
public Optional<String> getContentType() {
return Optional.ofNullable(contentType);
}
@Override
public Optional<String> getName() {
return Optional.of(path.getFileName().toString());
}
@Override
public OptionalLong getSize() {
try {
return OptionalLong.of(Files.size(path));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
CICBlob streamedFileBlob = new FileCICBlob(Paths.get("invoice.pdf"), "application/pdf", "sha256:abc123");- Package:
org.hyland.sdk.cic.ingest - Provides APIs for interacting with the CIC Ingest service:
IngestService: High-level API for ingest operations (e.g., uploading blobs, managing pre-signed URLs, sending single or batchedIngestEvents).IngestHttpClient: Lower-level HTTP client, extendsAbstractAuthenticatedHttpClientfor direct HTTP interactions.
- Design notes:
IngestEventproperties are held byIngestEventProperties, an immutable, typed container keyed by property name, whose values are modeled with theIngestEventPropertyhierarchy (IngestEventPropertyValuefor scalars/arrays/objects,IngestEventPropertyFilefor blob-backed properties). UseIngestEvent.Builder.putProperty(...)for individual properties, orproperties(IngestEventProperties)to set them all at once.IngestService.ingest(IngestEvent)andingest(IngestEvent.Batch)automatically upload anyIngestEventPropertyFileblobs (viauploadBlobsIfNeeded) before sending the event(s), so callers don't need to manage pre-signed URLs manually for property-attached blobs.PropertyArrayis deprecated since 1.1.0, in favor of the typedIngestEventPropertyValue/IngestEvent.Builder#putProperty(String, T[])overloads; it is still accepted byIngestEventProperties.Builder.put(String, PropertyArray)/IngestEvent.Builder.putProperty(String, PropertyArray)for backward compatibility.
Example Usage:
AuthenticationHttpClient.Builder authBuilder = AuthenticationHttpClient.from("my-token-uri")
.clientId("my-client-id")
.clientSecret("my-client-secret");
IngestHttpClient client = IngestHttpClient.from("https://ingestion.insight.dev.experience.hyland.com", authBuilder)
.sourceId("my-source-id")
.hxpEnvironment("my-hxp-environment")
.build();
IngestService service = new IngestService(client);
// Build and send an event with typed properties, including a blob-backed file property
IngestEvent event = IngestEvent.builder(IngestEvent.Type.CREATE, documentId)
.putProperty("title", "Invoice #123")
.putProperty("file:content", IngestEventPropertyFile.builder(blob).build())
.build();
service.ingest(event); // uploads "file:content"'s blob automatically before sending
// Send several events in a single batch
IngestEvent event2 = IngestEvent.builder(IngestEvent.Type.CREATE, "other-document-id")
.putProperty("title", "Invoice #124")
.build();
service.ingest(IngestEvent.Batch.of(event, event2));Example: Annotating a property
IngestEventPropertyValue.Builder#annotation(Serializable) (a shortcut for extra("annotation", value)) attaches metadata to a
property, e.g. to tell the CIC Ingest service how to interpret its value:
IngestEvent event = IngestEvent.builder(IngestEvent.Type.CREATE, documentId)
.putProperty("allAncestorIds",
IngestEventPropertyValue.builder(new String[] { "id1", "id2" }).annotation("ancestorIds").build())
.build();- Package:
org.hyland.sdk.cic.agent - Provides APIs for managing CIC agents and submitting questions to them:
AgentService: High-level API for agent lifecycle operations (create, read, update, delete), agent versioning, avatar management, LLM model and guardrail discovery, and question submission.AgentHttpClient: Lower-level HTTP client, extendsAbstractAuthenticatedHttpClientfor direct HTTP interactions.
- Design notes:
- Exposes a fluent, resource-oriented API.
AgentService.agent(agentId)returns anAgentResourcehandle that scopes subsequent operations to a single agent without repeatedly passing the ID. - Integration-specific operations are available via
AgentService.integrations(), which returns anIntegrationAgentService. - Mutating operations accept either a fully built request object or a
Consumerof its builder for concise, inline configuration.
- Exposes a fluent, resource-oriented API.
Example Usage:
AgentHttpClient client = AgentHttpClient.from("https://discovery.dev.experience.hyland.com/agent", authBuilder)
.hxpEnvironment("my-hxp-environment")
.build();
AgentService service = new AgentService(client);
// Create an agent using a builder consumer
AgentConfiguration agent = service.createAgent(builder -> builder
.name("Support Assistant")
.description("Answers product support questions"));
// Submit a question via a resource handle
QuestionResponse response = service.agent(agent.id())
.submitQuestion(request -> request.question("How do I reset my password?"));- Package:
org.hyland.sdk.cic.qna - Provides APIs for question-and-answer and conversational interactions with CIC agents:
QnaService: High-level API for submitting questions, managing multi-turn conversations, retrieving answers and question history, and submitting feedback.QnaHttpClient: Lower-level HTTP client, extendsAbstractAuthenticatedHttpClientfor direct HTTP interactions.
- Design notes:
- Follows the same resource-oriented model as the Agent module.
QnaService.agent(agentId),agent(agentId).conversation(conversationId), andquestion(questionId)return scoped resource handles for conversation, message, and question operations. - Integration-specific operations are available via
QnaService.integrations(), which returns anIntegrationQnaService. - List operations return paginated responses and provide
*Paginator()variants returning lazily-fetching iterables (CursorPageIterable,PageIterable) that fetch pages on demand.
- Follows the same resource-oriented model as the Agent module.
Example Usage:
QnaHttpClient client = QnaHttpClient.from("https://discovery.dev.experience.hyland.com/qna", authBuilder)
.hxpEnvironment("my-hxp-environment")
.build();
QnaService service = new QnaService(client);
// Start a conversation and send a follow-up message
StartConversationResponse started = service.agent(agentId)
.startConversation(request -> request.question("How do I onboard a new team?"));
ConversationMessage message = service.agent(agentId)
.conversation(started.conversation().id())
.sendMessage(request -> request.question("What are the next steps?"));
// Iterate over all question history entries across pages
for (QuestionHistory entry : service.agent(agentId).getQuestionHistoryPaginator()) {
// process entry
}- Package:
org.hyland.sdk.cic.nucleus - Provides APIs for CIC Nucleus platform and identity operations, split across two authenticated HTTP clients:
NucleusHttpClient: Client for the System Integrations API. Wrapped bySystemIntegrationService, a high-level API for managing systems, groups, group members, and user mappings (including their attributes).NucleusIAMHttpClient: Client for the Nucleus IAM API. Wrapped byUsersService, a high-level API for user lookup and enumeration.
- Design notes:
- Both services follow the resource-oriented model.
SystemIntegrationService.system(systemId)andprincipalUser(principalUserId)return scoped resource handles; list operations expose paginated responses with lazily-fetching*Paginator()variants. UsersServiceretrieves interactive users only. The IAM API surfaces interactive users;listUsers,listUsersPaginator, andgetUserreturnInteractiveUserinstances and do not enumerate service or system accounts.
- Both services follow the resource-oriented model.
Example Usage:
// System integration operations
NucleusHttpClient nucleusClient = NucleusHttpClient.from("https://api.platform.dev.app.hyland.com", authBuilder)
.build();
SystemIntegrationService systems = new SystemIntegrationService(nucleusClient);
SystemOutputPage page = systems.listSystems();
// Interactive user lookup via the IAM API
NucleusIAMHttpClient iamClient = NucleusIAMHttpClient.from("https://auth.dev.app.hyland.com", authBuilder)
.build();
UsersService users = new UsersService(iamClient);
for (InteractiveUser user : users.listUsersPaginator()) {
// process interactive user
}- Artifact:
org.hyland.sdk:cic-ke - Package:
org.hyland.sdk.cic.ke - Provides authenticated clients and high-level services for three related API areas:
- Context API v2:
KEHttpClientexposes low-level upload, process, result, action-discovery, and health operations.KEServiceadds end-to-end upload/enrichment workflows and result polling. - Data Curation API:
DataCurationHttpClientexposes presign, upload, job-status, result-download, and embedding-model operations.DataCurationServicecoordinates curation workflows and terminal job handling. - Configuration API: The Data Curation client and service manage processing defaults, conditional configuration rules, and rule dry runs.
- Context API v2:
- Content references: Process requests can identify content by an uploaded object path or an existing platform document ID.
- Mapping:
KEMapperFactoryregisters Context, Data Curation, and Configuration request/response mappers through the common SDK mapper SPI.
Maven Dependency:
<dependency>
<groupId>org.hyland.sdk</groupId>
<artifactId>cic-ke</artifactId>
<version>${cic-java-sdk.version}</version>
</dependency>The SDK also requires a runtime serializer such as cic-http-client-jackson2.
Example Usage:
KEHttpClient client = KEHttpClient.from("https://ke.api.example.hyland.com", authBuilder)
.hxpEnvironment("my-hxp-environment")
.build();
KEService service = new KEService(client);
// blob is a CICBlob containing the document to upload and enrich.
EnrichmentResult result = service.enrich(blob, request -> request
.action(Action.TEXT_SUMMARIZATION)
.action(Action.NAMED_ENTITY_RECOGNITION_TEXT));For authentication setup, CICBlob examples, supported actions, document-ID processing, Data Curation,
Configuration API operations, polling, response models, and error handling, see the
cic-ke module documentation.
- New Modules:
- Extend
AbstractAuthenticatedHttpClientand its builder for new CIC service modules.
- Extend
- Custom Serialization/Mapping:
- Implement and register
SerializerFactoryandMapperFactoryfor new formats or business objects.
- Implement and register
- Reference Classes:
- See
cic-http-client,cic-http-client-jackson2,cic-ingest, andcic-kemodules for examples.
- See
Best Practices:
- Use builders for configuration and instantiation.
- Register SPI implementations for modularity.
- Write unit tests for custom mappers and clients.
This SDK is designed for:
- SDK Users: Developers integrating Hyland CIC services into their Java applications.
- Contributors: Developers extending the SDK with new modules, mappers, or marshallers.
- The project uses Maven for build and test automation.
- GitHub Actions workflow (
.github/workflows/build.yml) compiles and tests the SDK on push and pull requests.
Licensed under the Apache License, Version 2.0.
Contributions are welcome! Please open issues or pull requests for improvements or new features.