Java
Install and configure the GoodMem Java SDK. Reference synchronous and asynchronous clients, resource methods, errors, and shared data models.
The GoodMem Java SDK offers an OpenAI-style API where any operation is accessed through client.<namespace>.<method>(...) on a Goodmem instance.
Installation
Available on Maven Central as ai.pairsys:goodmem-java.
// build.gradle.kts
repositories { mavenCentral() }
dependencies {
implementation("ai.pairsys:goodmem-java:0.2.2")
}<!-- pom.xml -->
<dependency>
<groupId>ai.pairsys</groupId>
<artifactId>goodmem-java</artifactId>
<version>0.2.2</version>
</dependency>Requirements: JDK 21 LTS. Brings in OkHttp 4 (HTTP) and Jackson 2.18 (JSON) transitively.
Clients
The Java SDK ships two clients with identical construction surfaces and parallel methods:
Goodmem— synchronous. Every method returns the response directly or throws.AsyncGoodmem— asynchronous. Every method returnsCompletableFuture<T>.
Most of the usage details in this page apply equally to both; code samples default to the sync client. For the async surface see Async client below.
Construction
Goodmem is constructed via its Builder in two mutually exclusive patterns.
Pattern 1 — Simple mode (baseUrl + apiKey):
import ai.pairsys.goodmem.client.Goodmem;
Goodmem client = Goodmem.builder()
.baseUrl("http://localhost:8080")
.apiKey("gm_...")
.timeout(java.time.Duration.ofSeconds(60)) // optional; defaults to 30s
.build();baseUrl— Goodmem server URL, e.g.,"http://localhost:8080"(nov1suffix)apiKey— Goodmem API key, e.g.,"gm_..."timeout— Call/connect/read/write timeout. Defaults to 30 seconds. Increase for long operations like RAG retrieval with LLM generation.
Pattern 2 — http_client mode (custom OkHttpClient):
import okhttp3.OkHttpClient;
Goodmem client = Goodmem.builder()
.baseUrl("http://localhost:8080")
.apiKey("gm_...")
.httpClient(myOkHttpClient) // fully configured by you
.build();Use this when you need interceptors (logging, tracing, retry), a proxy, a custom TLS trust store, or connection pool tuning. You own the client's lifecycle — goodmem.close() is a no-op under this mode.
try-with-resources
Goodmem implements AutoCloseable. Use it as a resource so the connection pool is released deterministically:
try (Goodmem client = Goodmem.builder()
.baseUrl("http://localhost:8080")
.apiKey("gm_...")
.build()) {
// use client
}Quickstart
import ai.pairsys.goodmem.client.Goodmem;
import ai.pairsys.goodmem.client.models.*;
try (Goodmem client = Goodmem.builder()
.baseUrl("http://localhost:8080")
.apiKey("gm_...")
.build()) {
// Create an embedder. Registry auto-fills provider, endpoint,
// dimensionality, max_sequence_length, distribution_type from the
// model_identifier; apiKey is converted to structured credentials.
EmbedderResponse embedder = client.embedders.create(
EmbedderCreationRequest.builder()
.displayName("My OpenAI")
.modelIdentifier("text-embedding-3-large")
.build(),
"sk-..."
);
// Create a space. DEFAULT_CHUNKING_CONFIG is auto-injected when unset.
Space space = client.spaces.create(
SpaceCreationRequest.builder()
.name("My Space")
.label("env", "dev")
.spaceEmbedders(java.util.List.of(new SpaceEmbedderConfig(embedder.embedderId(), null)))
.build());
// Pagination is automatic — iterate across all pages.
for (Memory m : client.memories.list(space.spaceId())) {
System.out.println(m.memoryId());
}
}TLS configuration
Four situations cover the typical needs:
1. Default — trusts system CAs
No configuration needed. Works for any server with a cert from a public CA.
2. Skip verification (localhost / dev)
Build an OkHttpClient that trusts all certificates, then pass it in http_client mode. Never use in production — an attacker in the middle can impersonate the server:
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
TrustManager[] trustAll = new TrustManager[] {
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] c, String a) {}
public void checkServerTrusted(X509Certificate[] c, String a) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
};
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, trustAll, new java.security.SecureRandom());
OkHttpClient http = new OkHttpClient.Builder()
.sslSocketFactory(ctx.getSocketFactory(), (X509TrustManager) trustAll[0])
.hostnameVerifier((host, session) -> true)
.build();
Goodmem client = Goodmem.builder()
.baseUrl("https://localhost:8081")
.apiKey("gm_...")
.httpClient(http)
.build();3. Custom CA — trusts only that CA
Load your CA into a KeyStore and build a TrustManagerFactory:
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import javax.net.ssl.*;
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
try (var in = new java.io.FileInputStream("/path/to/rootCA.pem")) {
ks.setCertificateEntry("ca", CertificateFactory.getInstance("X.509").generateCertificate(in));
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
OkHttpClient http = new OkHttpClient.Builder()
.sslSocketFactory(ctx.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0])
.build();4. Custom CA + system CAs
If you need to trust both your CA and the default system trust store, load both into a KeyStore:
KeyStore defaultKs = KeyStore.getInstance(KeyStore.getDefaultType());
defaultKs.load(null);
TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
defaultTmf.init((KeyStore) null); // loads system CAs
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
// add default CAs
for (TrustManager tm : defaultTmf.getTrustManagers()) {
if (tm instanceof X509TrustManager xtm) {
int i = 0;
for (X509Certificate c : xtm.getAcceptedIssuers()) ks.setCertificateEntry("sys-" + i++, c);
}
}
// add your CA
try (var in = new java.io.FileInputStream("/path/to/rootCA.pem")) {
ks.setCertificateEntry("mine", CertificateFactory.getInstance("X.509").generateCertificate(in));
}
// then build TrustManagerFactory + OkHttpClient as in #3Proxy
OkHttpClient http = new OkHttpClient.Builder()
.proxy(new java.net.Proxy(Proxy.Type.HTTP, new java.net.InetSocketAddress("proxy.example", 8080)))
.build();Request logging
import okhttp3.logging.HttpLoggingInterceptor;
HttpLoggingInterceptor log = new HttpLoggingInterceptor();
log.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient http = new OkHttpClient.Builder().addInterceptor(log).build();Requires the extra dep: com.squareup.okhttp3:logging-interceptor.
Methods
Each namespace below links to its full class Javadoc on javadoc.io — that's where the per-method @param contract, @throws block, Example code, and REST equivalent all live. The MDX page below this section is the narrative reference; the Javadoc is the per-method reference.
access_policy
Direct grants + scoped role assignments for resource access policy.
Javadoc: ai.pairsys.goodmem.client.api.AccessPolicyAPI
| Method | Description |
|---|---|
check | Check effective authorization |
grantsCreate | Create an authorization grant |
grantsDelete | Revoke an authorization grant |
grantsGet | Get an authorization grant |
grantsList | List authorization grants |
roleAssignmentsCreate | Assign a scoped role |
roleAssignmentsDelete | Revoke a scoped role assignment |
roleAssignmentsGet | Get a scoped role assignment |
roleAssignmentsList | List scoped role assignments |
admin
Server lifecycle ops — drain, license reload, background-job purge.
Javadoc: ai.pairsys.goodmem.client.api.AdminAPI
| Method | Description |
|---|---|
backgroundJobsPurge | Purge completed background jobs |
drain | Request the server to enter drain mode |
licenseReload | Reload the active license from disk |
retrieveMemoryLogPoliciesCreate | Create a RetrieveMemory log policy |
retrieveMemoryLogPoliciesDelete | Delete a RetrieveMemory log policy |
retrieveMemoryLogPoliciesGet | Get a RetrieveMemory log policy |
retrieveMemoryLogPoliciesList | List RetrieveMemory log policies |
transferInstanceOwnership | Transfer GoodMem instance ownership |
apikeys
API key lifecycle — create, list, update, soft-delete.
Javadoc: ai.pairsys.goodmem.client.api.ApikeysAPI
| Method | Description |
|---|---|
create | Create a new API key |
delete | Delete an API key Permanently revokes an API key and immediately rejects it for future authentication |
get | Get an API key |
list | List API keys |
update | Update an API key |
embedders
Embedder management — provider configuration + lifecycle.
Javadoc: ai.pairsys.goodmem.client.api.EmbeddersAPI
| Method | Description |
|---|---|
create | Create a new embedder |
delete | Delete an embedder |
get | Get an embedder by ID |
list | List embedders |
update | Update an embedder |
instance
Singleton GoodMem instance identity, ownership, and audit metadata.
Javadoc: ai.pairsys.goodmem.client.api.InstanceAPI
| Method | Description |
|---|---|
get | Get the GoodMem instance |
llms
LLM management — generation-time model registration.
Javadoc: ai.pairsys.goodmem.client.api.LlmsAPI
| Method | Description |
|---|---|
create | Create a new LLM |
delete | Delete an LLM |
get | Get an LLM by ID |
list | List LLMs |
update | Update an LLM |
memories
Memory CRUD + retrieval (streaming) + file upload + batch ops.
Javadoc: ai.pairsys.goodmem.client.api.MemoriesAPI
| Method | Description |
|---|---|
batchCreate | Create multiple memories in a single batch |
batchDelete | Delete memories in batch |
batchGet | Get multiple memories by ID |
content | Download memory content |
create | Create a memory from text content or base64-encoded binary content |
delete | Delete a memory |
get | Get a memory by ID |
list | Lists memories within a given space |
pages | List memory page images |
pagesImage | Download memory page image content |
retrieve | Performs a streaming semantic search across one or more memory spaces and returns matching chunks ranked by relevance as well as… |
ocr
Document OCR — text extraction from PDFs / images.
Javadoc: ai.pairsys.goodmem.client.api.OcrAPI
| Method | Description |
|---|---|
document | Run OCR on a document or image |
ping
Endpoint health probes — single-shot and streaming.
Javadoc: ai.pairsys.goodmem.client.api.PingAPI
rerankers
Reranker management — re-scoring of retrieval hits.
Javadoc: ai.pairsys.goodmem.client.api.RerankersAPI
| Method | Description |
|---|---|
create | Create a new reranker |
delete | Delete a reranker |
get | Get a reranker by ID |
list | List rerankers |
update | Update a reranker |
service_identities
Production service identity creation, ownership, and lifecycle.
Javadoc: ai.pairsys.goodmem.client.api.ServiceIdentitiesAPI
| Method | Description |
|---|---|
create | Create a service identity |
delete | Delete a service identity |
get | Get a service identity |
list | List service identities |
transferOwnership | Transfer service-identity ownership |
update | Update a service identity |
spaces
Memory space management — the top-level container for memories.
Javadoc: ai.pairsys.goodmem.client.api.SpacesAPI
| Method | Description |
|---|---|
create | Create a new Space |
delete | Delete a space |
get | Get a space by ID |
list | List spaces accessible to the caller, with optional filtering by owner, labels, and name |
transferOwnership | Transfer ownership of a space |
update | Update a space |
system
Server info + system initialization.
Javadoc: ai.pairsys.goodmem.client.api.SystemAPI
user_enrollments
One-time human-user enrollment completion.
Javadoc: ai.pairsys.goodmem.client.api.UserEnrollmentsAPI
| Method | Description |
|---|---|
complete | Complete human-user enrollment |
users
User lookup by id, email, or me.
Javadoc: ai.pairsys.goodmem.client.api.UsersAPI
| Method | Description |
|---|---|
create | Create a human user |
createEnrollment | Create a human-user enrollment |
delete | Delete a human user |
get | Retrieves a user by ID or email address |
getByUsername | Get user by username |
getEnrollment | Get a human-user enrollment |
list | List human users |
listEnrollments | List a human user's enrollments |
me | Get current user profile |
revokeEnrollment | Revoke a human-user enrollment |
update | Update a human user |
Request builders
Every request record (EmbedderCreationRequest, SpaceCreationRequest, JsonMemoryCreationRequest, RetrieveMemoryRequest, RerankerCreationRequest, LLMCreationRequest, etc.) exposes a nested Builder so you can set only the fields you care about and skip the long positional null, null, null, … constructor:
EmbedderCreationRequest req = EmbedderCreationRequest.builder()
.displayName("My OpenAI")
.modelIdentifier("text-embedding-3-large")
.build();Map<String, String> fields named labels, hints, or metadata get a singular-form convenience setter that appends to the map:
SpaceCreationRequest req = SpaceCreationRequest.builder()
.name("My Space")
.label("env", "dev") // appends to labels
.label("team", "search")
.spaceEmbedders(List.of(new SpaceEmbedderConfig(embedderId, null)))
.build();Under the hood the Builder just calls the record's positional constructor. Null unset fields are omitted from the wire payload thanks to @JsonInclude(NON_NULL). You can still use the positional constructor directly if you prefer — the Builder is purely additive.
Typed list/get options
List and get methods expose a typed XxxListOptions / XxxGetOptions record so you don't have to remember wire-format keys (maxResults, sortOrder, …) and can let the IDE autocomplete them:
Page<Space> page = client.spaces.list(
SpaceListOptions.builder()
.maxResults(50)
.nameFilter("research*")
.sortOrder(SortOrder.ASCENDING)
.label("env", "prod") // flattened to label.env=prod on the wire
.build());Null-valued fields are dropped from the query string. Map<String, String> fields named label are flattened into label.<key>=<value> entries to match the server's label-filter syntax. Passing null for the options argument is equivalent to an empty filter.
Eight options classes are generated:
| Options type | Method |
|---|---|
SpaceListOptions | spaces.list(options) |
EmbedderListOptions | embedders.list(options) |
LLMListOptions | llms.list(options) |
RerankerListOptions | rerankers.list(options) |
MemoryListOptions | memories.list(spaceId, options) |
MemoryGetOptions | memories.get(id, options) |
MemoryPageListOptions | memories.pages(id, options) |
MemoryPageImageOptions | memories.pagesImage(id, pageIndex, options) |
The original Map<String, Object> overload is still available as an escape hatch — it's renamed to listRaw(...) / getRaw(...) / pagesRaw(...) / pagesImageRaw(...) so the primary method name belongs to the typed path:
// Typed (preferred):
client.spaces.list(SpaceListOptions.builder().maxResults(50).build());
// Raw escape hatch:
client.spaces.listRaw(Map.of("maxResults", 50));UUID-typed path parameters
Every get / delete / update method that takes a resource id offers a java.util.UUID overload alongside the String form. The generator converts via .toString() internally:
UUID spaceUuid = UUID.fromString("...");
Space typed = client.spaces.get(spaceUuid); // UUID-typed
Space raw = client.spaces.get("sp_..."); // String-typed (escape hatch)memories.list(spaceId, options) and memories.pagesImage(id, pageIndex, ...) also get UUID + long pageIndex overloads, plus a combined (UUID, long, ...) variant so the strongly-typed signatures compose without forcing you to mix.
Factory methods for oneOf models
Records with an "exactly one of" contract (ChunkingConfiguration, ContextItem, PingEvent, RetrievedItem) expose static factory methods per variant, so you can't accidentally hand-null sibling fields:
ChunkingConfiguration cfg = ChunkingConfiguration.recursive(
RecursiveChunkingConfiguration.builder()
.chunkSize(512)
.chunkOverlap(64)
.build());
RetrievedItem item = RetrievedItem.memory(memory);Encoded-primitive convenience setters
The Builder for each request record overloads a handful of high-churn fields with stronger Java types:
| Where | String/Long base | Typed overload | What it does |
|---|---|---|---|
endpointUrl / apiPath / monitoringEndpoint | String | java.net.URI | .toString() |
*Ms timeout fields | Long | java.time.Duration | .toMillis() |
*Sec timeout fields | Long | java.time.Duration | .toSeconds() |
*At timestamp fields (e.g. expiresAt) | Long | java.time.Instant | .toEpochMilli() |
JsonMemoryCreationRequest.originalContent | String | byte[] | base64-encodes into originalContentB64 |
RetrieveMemoryRequest.postProcessor | PostProcessor | ChatPostProcessorConfig | .toPostProcessor() |
CreateApiKeyRequest req = CreateApiKeyRequest.builder()
.label("scope", "ci")
.expiresAt(Instant.now().plus(Duration.ofDays(30)))
.build();Use ai.pairsys.goodmem.client.MediaTypes for common contentType values: TEXT_PLAIN, APPLICATION_PDF, IMAGE_PNG, etc. — avoids string-literal typos.
Typed configs for the built-in RAG chat processor
ai.pairsys.goodmem.client.ChatPostProcessorConfig is a fluent, validated builder for the ChatPostProcessor factory — the common RAG pipeline. Numeric fields enforce documented bounds where applicable (llmTemp [0, 2], positive token budgets, positive maxResults):
RetrieveMemoryRequest req = RetrieveMemoryRequest.builder()
.message("What do you know about AI?")
.spaceKeys(List.of(new SpaceKey(spaceId, null, null)))
.postProcessor(ChatPostProcessorConfig.builder()
.llmId(llmId)
.genTokenBudget(2048L)
.llmTemp(0.2)
.build())
.build();For non-built-in processors, keep using new PostProcessor(factoryName, configMap) directly.
Typed status enums
Four IR-declared status fields now deserialize as typed Java enums instead of raw strings — so pattern matching and switch on known states works:
Memory.processingStatus→MemoryProcessingStatusApiKeyResponse.status/UpdateApiKeyRequest.status→ApiKeyStatusBackgroundJobSummary.status→BackgroundJobStatusAdminDrainResponse.state→AdminDrainState
Unknown server values fail Jackson deserialization loudly; if the server adds a new state, regenerate from an updated IR.
Range validation on Builder setters
Setters on documented numeric bounds throw IllegalArgumentException at call time rather than waiting for the server to reject. Null still clears the field. Covered today:
| Field | Bound |
|---|---|
dimensionality, maxSequenceLength, chunkSize, maxResults, limit, count, efSearch, maxScan, maxInFlight | ≥ 1 |
chunkOverlap | ≥ 0 |
temperature | [0.0, 2.0] |
topP | [0.0, 1.0] |
frequencyPenalty, presencePenalty | [-2.0, 2.0] |
Common Data Models
Types shared across multiple API namespaces. Models are Java records with Jackson annotations; JSON wire names are camelCase (matching the record component names).
Page<T>
ai.pairsys.goodmem.client.Page<T> — a page of results from a list endpoint. Returned by every method that auto-paginates (spaces.list, memories.list). Iterates across all pages lazily; subsequent pages are fetched on demand. Page<T> does not implement AutoCloseable — the underlying HTTP connection is released per call, so there's nothing to close.
Page<Space> page = client.spaces.list(SpaceListOptions.builder().maxResults(50).build());
// First-page items only
List<Space> first = page.items();
// Auto-paginate through everything (lazy)
for (Space s : page) { process(s); }
// Manual page advance
while (page.hasMore()) {
page = page.next();
for (Space s : page.items()) { process(s); }
}items()→List<T>— items in this pagenextToken()→String— continuation token, ornullif exhaustedhasMore()→boolean— true iffnextToken != nullnext()→Page<T>— fetch next page; throwsNoSuchElementExceptionwhen exhaustediterator()→Iterator<T>— lazy cross-page iteration; can only be called once
RetrieveMemoryStream
ai.pairsys.goodmem.client.RetrieveMemoryStream — iterable, closeable NDJSON stream of RetrieveMemoryEvent returned by memories.retrieve. Each newline-delimited JSON document is parsed into one event. Always use try-with-resources to release the underlying HTTP connection:
RetrieveMemoryRequest req = RetrieveMemoryRequest.builder()
.message("question")
.spaceId(spaceId)
.build();
try (RetrieveMemoryStream events = client.memories.retrieve(req)) {
for (RetrieveMemoryEvent evt : events) {
process(evt);
}
}The iterator can only be consumed once — NDJSON cannot be rewound. To restart, call memories.retrieve again.
Defaults
ai.pairsys.goodmem.client.Defaults — hand-written SDK default constants. Currently exposes:
-
DEFAULT_CHUNKING_CONFIG(ChunkingConfiguration) — recursive chunking at 512 characters with 64-character overlap,KEEP_ENDboundary strategy, measured by characters. Auto-injected byspaces.createwhendefaultChunkingConfigis null. Reference it directly if you want to start from the defaults and tweak:import ai.pairsys.goodmem.client.Defaults; ChunkingConfiguration base = Defaults.DEFAULT_CHUNKING_CONFIG;
AccessPolicyRule
One operation, selector, and optional assigned resource.
operation(Operation) — Protected operation.selector(Selector) — Resource-selection semantics.assignedResource(AccessPolicyTarget, optional) — Required exactly for EXACT and DIRECT_MEMBERS_OF selectors.
AccessPolicyTarget
A typed access-policy target. resourceId is omitted for INSTANCE and required otherwise.
kind(ResourceKind) — Concrete target kind.resourceId(String, optional) — Concrete resource UUID; omitted for the singleton INSTANCE target.
ApiKeyAuth
Configuration for classic API-key authentication.
inlineSecret(String, optional) — Secret stored directly in GoodMem (mutually exclusive with secretRef)secretRef(SecretReference, optional) — Reference to an external secret manager entry (mutually exclusive with inlineSecret)headerName(String, optional) — Desired HTTP header to carry the credential (defaults to Authorization)prefix(String, optional) — Optional prefix prepended to the secret (e.g., "Bearer ")
ApiKeyResponse
API key metadata without sensitive information.
apiKeyId(String) — Unique identifier for the API key.subjectPrincipalId(String) — Principal authenticated by this API key.ownerPrincipalId(String) — Principal that administratively owns this API key.authorityMode(ApiKeyAuthorityMode) — Immutable authority derivation mode.ceiling(List<AccessPolicyRule>, optional) — Complete immutable issuance ceiling; omitted only when ceilingOmitted is true.ceilingOmitted(Boolean) — True only when a BASIC list projection intentionally omitted the immutable ceiling.keyPrefix(String) — First few characters of the key for display/identification purposes.status(String) — Compatibility usability status. ACTIVE means USABLE; INACTIVE combines NOT_YET_VALID, EXPIRED, and REVOKED.lifecycleState(String) — Precise lifecycle state at the response evaluation instant.labels(Map<String, String>) — User-defined labels for organization and filtering.expiresAt(Long, optional) — Expiration timestamp in milliseconds since epoch. If not provided, the key does not expire.validFrom(Long) — Inclusive activation time in milliseconds since epoch.revokedAt(Long, optional) — Permanent revocation time in milliseconds since epoch.revokedById(String, optional) — Exact audit actor UUID that revoked this key.lastUsedAt(Long, optional) — Last time this API key was used, in milliseconds since epoch.createdAt(Long) — When the API key was created, in milliseconds since epoch.updatedAt(Long) — When the API key was last updated, in milliseconds since epoch.createdById(String) — Exact principal or API-key actor that created this API key.updatedById(String) — Exact principal or API-key actor that last updated this API key.
ChunkingConfiguration
Configuration for text chunking strategy used when processing content. Exactly one of none, recursive, or sentence must be provided.
none(NoChunkingConfiguration, optional) — No chunking strategy - preserve original content as single unitrecursive(RecursiveChunkingConfiguration, optional) — Recursive hierarchical chunking strategy with configurable separatorssentence(SentenceChunkingConfiguration, optional) — Sentence-based chunking strategy with language detection
EndpointAuthentication
Structured credential payload describing how GoodMem should authenticate with an upstream provider.
kind(CredentialKind) — Selected credential strategyapiKey(ApiKeyAuth, optional) — Configuration when kind is CREDENTIAL_KIND_API_KEYgcpAdc(GcpAdcAuth, optional) — Configuration when kind is CREDENTIAL_KIND_GCP_ADClabels(Map<String, String>, optional) — Optional annotations to aid operators (e.g., "owner=vertex")
GcpAdcAuth
Configuration for Google Application Default Credentials (ADC).
scopes(List<String>, optional) — Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.quotaProjectId(String, optional) — Optional quota project used for billing
GoodMemInstance
The singleton GoodMem instance and its ownership audit metadata.
instanceId(String) — Durable UUID of the singleton GoodMem instance.ownerId(String) — Current human owner principal UUID.createdAt(Long) — Initialization timestamp in milliseconds since the Unix epoch.updatedAt(Long) — Most recent ownership-transfer timestamp in milliseconds since the Unix epoch.createdById(String) — Principal or API-key actor UUID that initialized the instance.updatedById(String) — Principal or API-key actor UUID that last transferred ownership.
GoodMemStatus
Warning or non-fatal status with granular codes (operation continues)
code(String) — Status code for the warning or informational messagemessage(String) — Human-readable status messagedetails(Map<String, String>, optional) — Additional contextual details
NoChunkingConfiguration
No chunking strategy - preserves original content as a single unit
(empty record)
RecursiveChunkingConfiguration
Recursive hierarchical chunking strategy with configurable separators and overlap
chunkSize(Long) — Maximum size of a chunk (should be ≤ context window)chunkOverlap(Long) — Sliding overlap between chunksseparators(List<String>, optional) — Hierarchical separator list (order = preference)keepStrategy(SeparatorKeepStrategy) — How to handle separators after splitting. KEEP_NONE is deprecated and behaves as KEEP_END.separatorIsRegex(Boolean, optional) — Whether separators are regex patternslengthMeasurement(LengthMeasurement) — How to measure chunk length
SecretReference
uri(String) — URI identifying where the secret can be resolved (e.g., vault://, env://)hints(Map<String, String>, optional) — Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})
SentenceChunkingConfiguration
Sentence-based chunking strategy with language detection support
maxChunkSize(Long) — Maximum size of a chunkminChunkSize(Long) — Minimum size before creating a new chunkenableLanguageDetection(Boolean, optional) — Whether to detect language for better segmentationlengthMeasurement(LengthMeasurement) — How to measure chunk length
TransferOwnershipRequest
Names the principal that will become the resource owner.
newOwnerId(String) — Existing principal UUID that will become the new owner.
ApiKeyAuthorityMode
INHERIT_SUBJECT follows a human subject's live authority. SCOPED intersects the subject's live authority with an immutable, nonempty issuance ceiling.
Java enum — INHERIT_SUBJECT, SCOPED.
CredentialKind
Credential kinds supported for upstream endpoint authentication.
Java enum — CREDENTIAL_KIND_UNSPECIFIED, CREDENTIAL_KIND_API_KEY, CREDENTIAL_KIND_GCP_ADC.
DashScopeApiDialect
DashScope request and response API dialect
Java enum — UNSPECIFIED, EMBEDDING_NATIVE_TEXT, EMBEDDING_NATIVE_CONTENTS, LLM_NATIVE_TEXT, LLM_NATIVE_MULTIMODAL, RERANK_NATIVE_NESTED, OPENAI_COMPATIBLE, RERANK_COMPATIBLE_FLAT.
DistributionType
Type of embedding distribution produced by the embedder
Java enum — DENSE, SPARSE.
GeminiApiBackend
Google API surface used by a Gemini embedder
Java enum — UNSPECIFIED, DEVELOPER, GOOGLE_CLOUD.
GrantAudienceAllAuthenticated
The literal true, selecting every successfully authenticated principal.
Java enum — True.
HnswIterativeScan
Iterative scan modes for HNSW index search.
Java enum — ITERATIVE_SCAN_UNSPECIFIED, ITERATIVE_SCAN_OFF, ITERATIVE_SCAN_RELAXED_ORDER, ITERATIVE_SCAN_STRICT_ORDER.
LLMProviderType
LLM provider types
Java enum — OPENAI, LITELLM_PROXY, OPEN_ROUTER, DASHSCOPE, VLLM, OLLAMA, LLAMA_CPP, CUSTOM_OPENAI_COMPATIBLE.
LengthMeasurement
Strategy for measuring chunk length during text splitting
Java enum — CHARACTER_COUNT, TOKEN_COUNT, CUSTOM.
Modality
Content modality types supported by embedders
Java enum — TEXT, IMAGE, AUDIO, VIDEO.
OcrCategory
Normalized OCR layout category.
Java enum — UNSPECIFIED, CAPTION, FOOTNOTE, FORMULA, LIST_ITEM, PAGE_FOOTER, PAGE_HEADER, PICTURE, SECTION_HEADER, TABLE, TEXT, TITLE, OTHER, UNKNOWN.
OcrInputFormat
OCR input format hint.
Java enum — AUTO, PDF, TIFF, PNG, JPEG, BMP.
Operation
Java enum — CREATE_USER, READ_USER, UPDATE_USER, DELETE_USER, LIST_USER, MANAGE_USER_ENROLLMENT, CREATE_SERVICE_IDENTITY, READ_SERVICE_IDENTITY, UPDATE_SERVICE_IDENTITY, DELETE_SERVICE_IDENTITY, LIST_SERVICE_IDENTITY, CREATE_SPACE, READ_SPACE, UPDATE_SPACE, DELETE_SPACE, LIST_SPACE, CREATE_API_KEY, READ_API_KEY, UPDATE_API_KEY, DELETE_API_KEY, LIST_API_KEY, CREATE_EMBEDDER, READ_EMBEDDER, UPDATE_EMBEDDER, DELETE_EMBEDDER, LIST_EMBEDDER, PING_EMBEDDER, EXECUTE_EMBEDDER, READ_EMBEDDER_CREDENTIALS, CREATE_RERANKER, READ_RERANKER, UPDATE_RERANKER, DELETE_RERANKER, LIST_RERANKER, PING_RERANKER, EXECUTE_RERANKER, READ_RERANKER_CREDENTIALS, CREATE_LLM, READ_LLM, UPDATE_LLM, DELETE_LLM, LIST_LLM, PING_LLM, EXECUTE_LLM, READ_LLM_CREDENTIALS, PROXY_INFERENCE_TARGET, OCR_DOCUMENT, CREATE_MEMORY, READ_MEMORY, DELETE_MEMORY, LIST_MEMORY, CREATE_EXTENSION, READ_EXTENSION, UPDATE_EXTENSION, DELETE_EXTENSION, LIST_EXTENSION, DOWNLOAD_EXTENSION, READ_INSTANCE, TRANSFER_INSTANCE_OWNERSHIP, TRANSFER_RESOURCE_OWNERSHIP, RELOAD_LICENSE, DRAIN_SERVER, PURGE_BACKGROUND_JOBS, CREATE_RETRIEVE_MEMORY_LOG_POLICY, READ_RETRIEVE_MEMORY_LOG_POLICY, LIST_RETRIEVE_MEMORY_LOG_POLICY, DELETE_RETRIEVE_MEMORY_LOG_POLICY, MANAGE_ACCESS.
PingPayloadType
Payload types supported by ping operations
Java enum — PAYLOAD_TYPE_UNSPECIFIED, TEXT, JSON, BINARY.
PingTargetType
Target types for ping operations
Java enum — TARGET_TYPE_UNSPECIFIED, EMBEDDER, RERANKER, LLM.
ProviderType
Embedding provider types
Java enum — OPENAI, VLLM, TEI, LLAMA_CPP, VOYAGE, COHERE, JINA, DASHSCOPE, GEMINI.
PurgeableBackgroundJobStatus
Terminal background job statuses eligible for purge requests.
Java enum — BACKGROUND_JOB_SUCCEEDED, BACKGROUND_JOB_FAILED, BACKGROUND_JOB_CANCELED.
ResourceKind
Java enum — INSTANCE, USER, SERVICE_IDENTITY, SPACE, API_KEY, EMBEDDER, RERANKER, LLM, MEMORY, EXTENSION, RETRIEVE_MEMORY_LOG_POLICY.
Role
Java enum — ROOT, ADMIN, USER, SPACE_VIEWER, SPACE_CONTRIBUTOR, SPACE_CONTENT_MANAGER, SPACE_ADMIN.
Selector
Java enum — ANY, OWN, EXACT, DIRECT_MEMBERS_OF.
SeparatorKeepStrategy
Strategy for handling separators after text splitting. KEEP_NONE is deprecated and treated as KEEP_END.
Java enum — KEEP_NONE, KEEP_START, KEEP_END.
SortOrder
Java enum — ASCENDING, DESCENDING, SORT_ORDER_UNSPECIFIED.
Errors
All SDK methods throw typed unchecked exceptions on HTTP errors. The hierarchy is rooted at GoodmemException, with ApiException for any HTTP 4xx/5xx and per-status subclasses for the common ones. Error class names align with the Python SDK's naming.
| Exception | HTTP | Description |
|---|---|---|
GoodmemException | — | Base exception for all SDK errors (also wraps I/O failures) |
ApiException | any 4xx/5xx | Generic HTTP error (has getStatusCode(), getBody()) |
BadRequestException | 400 | Malformed or invalid request |
AuthenticationException | 401 | Invalid or missing API key |
PermissionDeniedException | 403 | Insufficient permissions |
NotFoundException | 404 | Resource not found |
ConflictException | 409 | Conflict (e.g., duplicate id) |
UnprocessableEntityException | 422 | Invalid request parameters |
RateLimitException | 429 | Too many requests |
InternalServerException | 5xx | Server-side error |
import ai.pairsys.goodmem.client.errors.*;
try {
Memory memory = client.memories.get("nonexistent-id");
} catch (NotFoundException e) {
System.out.println("Memory not found");
} catch (ApiException e) {
System.out.println("API error " + e.getStatusCode() + ": " + e.getBody());
}All exceptions are RuntimeExceptions — no checked-exception surface.
File upload convenience
memories.create(String spaceId, Path filePath) reads a file from disk, base64-encodes it, infers a contentType via Files.probeContentType, and posts a JSON memory:
import java.nio.file.Path;
Memory memory = client.memories.create(
space.spaceId(),
Path.of("/tmp/report.pdf"));For large files consider streaming the bytes yourself or using an async upload in your own code — the built-in overload reads the file fully into memory before base64-encoding.
Javadoc
Every class, method, field, and parameter is documented in full Javadoc. javadoc.io hosts the
per-class API reference automatically once releases land on Maven Central:
https://javadoc.io/doc/ai.pairsys/goodmem-java/latest/.
Async client
AsyncGoodmem is a parallel client where every method returns
CompletableFuture<T> instead of blocking for the response. Construction
mirrors Goodmem exactly — same Builder, same two modes (simple /
custom OkHttpClient), same timeout / close semantics.
import ai.pairsys.goodmem.client.AsyncGoodmem;
import ai.pairsys.goodmem.client.models.SystemInfoResponse;
try (AsyncGoodmem client = AsyncGoodmem.builder()
.baseUrl("http://localhost:8080")
.apiKey("gm_...")
.build()) {
// Single call — `.get()` or `.join()` if you want to block here
SystemInfoResponse info = client.system.info().get();
// Composition — ideal for pipelines
EmbedderCreationRequest req = EmbedderCreationRequest.builder()
.displayName("My OpenAI")
.modelIdentifier("text-embedding-3-large")
.build();
client.embedders
.create(req, "sk-...")
.thenCompose(emb -> client.spaces.create(
SpaceCreationRequest.builder()
.name("My Space")
.spaceEmbedders(java.util.List.of(
new SpaceEmbedderConfig(emb.embedderId(), null)))
.build()))
.thenAccept(space -> System.out.println("Space ready: " + space.spaceId()))
.exceptionally(ex -> { ex.printStackTrace(); return null; })
.join();
}Under the hood, the async client uses OkHttp's native async callbacks —
no thread pool is required beyond OkHttp's own dispatcher, and the same
connection pool is shared across concurrent requests. If you need more
concurrency control (custom dispatcher executor, rate limiting, etc.),
build an OkHttpClient yourself and pass it via .httpClient(...).
Async pagination
List endpoints return CompletableFuture<AsyncPage<T>> instead of Page<T>.
AsyncPage<T> exposes the current page's items synchronously (they're
already loaded); .next() returns CompletableFuture<AsyncPage<T>> to
fetch the next page.
static CompletableFuture<List<Space>> collectAll(AsyncPage<Space> page, List<Space> acc) {
acc.addAll(page.items());
return page.hasMore()
? page.next().thenCompose(p -> collectAll(p, acc))
: CompletableFuture.completedFuture(acc);
}
client.spaces.list()
.thenCompose(first -> collectAll(first, new ArrayList<>()))
.thenAccept(all -> System.out.println("Got " + all.size() + " spaces"));AsyncPage<T> intentionally does not implement Iterable<T> — mixing
blocking iteration with async I/O is a footgun. Chain via .next() and
.thenCompose() instead.
Async streaming
memories.retrieve returns CompletableFuture<RetrieveMemoryStream> on
the async client. The future completes once the stream headers arrive;
the returned stream is consumed the same way as in the sync client (lazy
Iterable<RetrieveMemoryEvent> inside a try-with-resources block).
RetrieveMemoryRequest req = RetrieveMemoryRequest.builder()
.message("question")
.spaceId(spaceId)
.build();
client.memories.retrieve(req).thenAccept(events -> {
try (events) {
for (RetrieveMemoryEvent e : events) process(e);
}
}).join();Error handling
HTTP errors (4xx/5xx) and I/O failures complete the future exceptionally
with the same exception types as the sync client, wrapped by
CompletionException at the call site:
client.memories.get("missing")
.thenAccept(System.out::println)
.exceptionally(ex -> {
Throwable cause = ex.getCause(); // unwrap CompletionException
if (cause instanceof NotFoundException) {
System.out.println("Memory not found");
}
return null;
});Request validation errors — e.g., credential_check throwing when a SaaS
endpoint is targeted without credentials — are thrown synchronously,
before the HTTP call is scheduled. The rationale: that's a programmer
error, not an I/O event, and should fail fast.