LangChain4j
Write LangChain4j Documents to GoodMem and retrieve content with sources for Java agents and RAG applications.
Use GoodMem as the memory and retrieval service for your LangChain4j application. GoodMem handles chunking, embedding, storage and optional reranking. Your application uses standard LangChain4j Documents, content retrieval and AI Services.
| Component | Use it to |
|---|---|
GoodMemDocumentIngestor | Store Documents with their metadata, optionally waiting for indexing. |
GoodMemContentRetriever | Retrieve content with source metadata, filters and optional reranking. |
retriever.asTool() | Give an agent search access to application-configured spaces and filters. |
GoodMemTools | Let an agent manage spaces and memories through SDK-backed tools. |
Installation
Requires Java 21+ and LangChain4j 1.20.0+. Add this dependency to your pom.xml:
<dependency>
<groupId>io.github.bashareid</groupId>
<artifactId>goodmem-langchain4j</artifactId>
<version>0.2.0</version>
</dependency>The official GoodMem Java SDK and LangChain4j are included. For AI Services, add your chosen chat-model provider. When upgrading from 0.1, read the migration notes.
The Maven groupId, io.github.bashareid, remains a maintainer's personal publishing coordinate;
the Java package namespace is ai.pairsys.goodmem.langchain4j.
For a local server with a self-signed certificate, trust its certificate or local CA using a
Java truststore. Version 0.2 configures TLS through the SDK client and JVM; it has no verifySsl
switch. The local TLS guide
shows how to preserve normal certificate and hostname verification.
Store and retrieve documents
Set up GoodMem and create a space. Set GOODMEM_BASE_URL,
GOODMEM_API_KEY and GOODMEM_SPACE_ID. Optionally set GOODMEM_RERANKER_ID; reranking
does not require an LLM.
import ai.pairsys.goodmem.client.Goodmem;
import ai.pairsys.goodmem.langchain4j.GoodMemContentRetriever;
import ai.pairsys.goodmem.langchain4j.GoodMemDocumentIngestor;
import ai.pairsys.goodmem.langchain4j.GoodMemFilters;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.rag.query.Query;
import java.time.Duration;
import java.util.List;
import java.util.Map;
public class Quickstart {
public static void main(String[] args) {
String spaceId = System.getenv("GOODMEM_SPACE_ID");
try (Goodmem client = Goodmem.builder()
.baseUrl(System.getenv("GOODMEM_BASE_URL"))
.apiKey(System.getenv("GOODMEM_API_KEY"))
.build()) {
var ingestor = GoodMemDocumentIngestor.builder()
.client(client).spaceId(spaceId).build();
ingestor.ingestAndWait(List.of(Document.from(
"Customers may return unused items within 30 days.",
Metadata.from(Map.of("source", "handbook", "team", "support")))), Duration.ofMinutes(2));
var retriever = GoodMemContentRetriever.builder()
.client(client).spaceIds(List.of(spaceId))
.filterExpression(GoodMemFilters.textEquals("team", "support"))
.rerankerId(System.getenv("GOODMEM_RERANKER_ID"))
.build();
for (var content : retriever.retrieve(Query.from("What is the return policy?"))) {
System.out.println(content.textSegment());
}
}
}
}Your application owns the SDK client and closes it when finished. Reuse it across the ingestor, retriever and tools; configure transport settings on that client.
ingest stores whole documents, preserves metadata and returns accepted memory IDs in input
order. ingestAndWait(documents, timeout) additionally waits for indexing with a budget you
choose, using batch status reads. Each ingestion creates new memories. A partial-write failure
retains confirmed IDs in GoodMemIngestionException.createdMemoryIds() and the original cause.
Readiness failures use GoodMemIndexingException; pass its pendingMemoryIds() to
GoodMemIndexing.waitForMemories(client, ids, timeout) to resume waiting without another upload.
Connect an AI Service
With a configured ChatModel model and the retriever above:
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.Result;
interface Assistant {
Result<String> chat(String question);
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.contentRetriever(retriever)
.build();
Result<String> answer = assistant.chat("What is the return policy?");
System.out.println(answer.content());
answer.sources().forEach(source -> System.out.println(source.textSegment().metadata()));Each retrieved text segment carries stored metadata plus memory_id, chunk_id and
space_id. On metadata name collisions, the original maps remain available in the
goodmem_metadata JSON field. When available, source comes from stored metadata or the original content
reference. Result.sources() lets your application display citations alongside the answer.
For agent-directed search, register a named tool:
var searchTools = List.of(retriever.asTool("searchPolicies", "Search our customer return policies"));
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.tools(searchTools)
.build();This passes a typed List<AiServiceTool> to LangChain4j. Add other scoped retrievers to the list
with distinct names and descriptions. Each search tool exposes only the query. Spaces, filters
and the reranker remain under your application's control.
Filters and reranking
The filterExpression applies to every configured space. dynamicFilterExpression(Function<Query, String>)
can add a constraint from trusted application metadata; it is combined with the fixed filter
using AND. Both accept native GoodMem expressions. GoodMemFilters.textEquals("tenant", tenant)
escapes application-supplied text values; avoid inserting unescaped text into expressions.
See metadata filtering for the full syntax.
Set maxResults for the number of returned chunks and fetchK for the server candidate count,
with or without reranking. Defaults are five results and twenty candidates with a reranker. The retriever
preserves server order and scores. Scores are available in ContentMetadata.SCORE and, when
reranking is enabled, ContentMetadata.RERANKED_SCORE.
Tools, errors and async workflows
new GoodMemTools(client) provides eleven tools for space and memory operations. Retrieval
returns compact chunks with text, source, score and IDs, plus abstractReply, statuses and
partial. Useful chunks survive partial failures. Get-memory returns readable text by default;
binary content is omitted with a notice and remains available through the SDK.
Create-memory waits for the written memory by default so an agent can immediately search it.
File uploads are disabled unless the application supplies an upload directory through
new GoodMemTools(client, Duration.ofMinutes(2), Path.of("/srv/agent-uploads")). Paths and
symlinks escaping that directory are rejected.
Direct tool HTTP failures retain their SDK exception types. The content retriever and scoped
search tool throw for known non-informational statuses. Future server codes map to UNKNOWN
without aborting retrieval; the administrative retrieval tool preserves those diagnostics and
sets partial=true. Malformed streams and HTTP failures still throw.
For asynchronous AI Services, configure DefaultRetrievalAugmentor with
.contentRetriever(retriever).offloadBlocking(true) and pass it through .retrievalAugmentor(...).
LangChain4j runs blocking retrieval on its executor. Native nonblocking retrieval is not
provided.
The usage guide covers readiness timeouts, metadata types, pagination, diagnostics and live tests.