The LangChain4J AI Services API — Creating RAG services with Oracle AI Vector Search, OracleEmbeddingStore, and the Oracle Database 23ai
Introduction
LangChain4J has introduced a new AI Services high-level API that provides a high-level abstraction for Java developers working on GenAI applications.
AI Services eliminate the need to manually orchestrate multiple low-level components such as ChatModel, ChatMessage, and ChatMemory2. While these classic implementations provide flexibility, they often require extensive boilerplate and intricate coordination across prompt templates, memory management, output parsing, and Retrieval-Augmented Generation (RAG).
Its level of abstraction has been inspired by frameworks like Spring Data JPA, allowing Java developers to use declarative interfaces so that LangChain4J automatically generates proxy implementations that handle the underlying complexity, including chat memory for maintaining context across interactions, tool integration for function calling, and RAG for enriching responses with augmented context.
In this post, I’ll walk you through how I built a simple yet powerful RAG agent using the new AiServices class. This agent answers questions based on embedded knowledge from Oracle Database 23ai, maintains conversation history with chat memory, and incorporates moderation to ensure safe interactions.
So, without further ado, let’s get started!
Prerequisites
- JDK — Java Development Kit 22
- Oracle Database Free Release 23ai — Container Image
- Oracle JDBC Driver 23ai
- Your preferred Java IDE — Eclipse, IntelliJ, VS Code
- Apache Maven or Gradle
Step 1: Configure your Java application
Configure your pom.xml with the following dependencies, which include the Oracle JDBC driver, UCP (Universal Connection Pool), and the LangChain4J modules for Oracle Embedding Store, in-process embedding model, and other dependencies. Ensure you use the latest version of langchain4j-oracle.
Using Maven
<dependencies>
<!-- Oracle Database 23ai JDBC / UCP -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc17</artifactId>
<version>23.9.0.25.07</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ucp17</artifactId>
<version>23.9.0.25.07</version>
</dependency>
<!-- LangChain4j -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.3.0</version>
</dependency>
<!-- LangChain4j Oracle -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-oracle</artifactId>
<version>1.3.0-beta9</version>
</dependency>
<!-- LangChain4j OpenAI -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.3.0</version>
</dependency>
<!-- In-process embedding models-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-embeddings-all-minilm-l6-v2-q</artifactId>
<version>1.3.0-beta9</version>
</dependency>
<!-- SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>Step 2: Define the Java interface with AI Services annotations
The first component of our RAG agent is a simple Java interface, OracleRag.java, with the AI Services annotations. This is where we specify the system prompt template we want to use, moderation details, system input, and other configurations.
package com.oracle.dev.jdbc.langchain4j;
import dev.langchain4j.service.Moderate;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
public interface OracleRag {
@SystemMessage("You are an agent who answers questions based on the provided context. Use the retrieved information to give accurate and concise responses.")
@Moderate
String answer(@UserMessage String question);
}The @SystemMessage annotation sets the agent’s persona and instructions, ensuring responses are based on the retrieved context, based on our RAG integration.
The @UserMessage annotation marks the input parameter, that is, the question provided by the user, while the @Moderate annotation enables automatic content moderation. This declarative style is the magic of AI Services!
Step 2: Define the Java class with the concrete implementation of our AI Services
Here’s the complete implementation of our Java service class, that is, the OracleRagService.java concrete implementation.
package com.oracle.dev.jdbc.langchain4j;
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
import static dev.langchain4j.model.openai.OpenAiModerationModelName.TEXT_MODERATION_LATEST;
import java.sql.SQLException;
import javax.sql.DataSource;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2q.AllMiniLmL6V2QuantizedEmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiModerationModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.ModerationException;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.oracle.CreateOption;
import dev.langchain4j.store.embedding.oracle.OracleEmbeddingStore;
public class OracleRagService {
public static void main(String[] args) throws SQLException {
// Get Oracle JDBC Pooled DataSource
DataSource dataSource = OracleDbUtils.getPooledDataSource();
// Initialize Oracle Embedding Store
EmbeddingStore<TextSegment> embeddingStore = OracleEmbeddingStore.builder().dataSource(dataSource)
.embeddingTable("rag_ai_service_embeddings", CreateOption.CREATE_OR_REPLACE).build();
// Initialize embedding model
EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
OpenAiModerationModel moderationModel = OpenAiModerationModel.builder().apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(TEXT_MODERATION_LATEST).build();
// Initialize content retriever for RAG
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore)
.embeddingModel(embeddingModel).maxResults(10).minScore(0.5).build();
// Initialize Chat Model (OpenAI in this case)
ChatModel chatModel = OpenAiChatModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).modelName(GPT_4_O_MINI)
.build();
// Initialize chat memory for a single user
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder().maxMessages(50).build();
// Add sample data to the embedding store
TextSegment segment1 = TextSegment
.from("Oracle Database 23ai introduces AI Vector Search for efficient similarity searches.");
Embedding embedding1 = embeddingModel.embed(segment1).content();
embeddingStore.add(embedding1, segment1);
TextSegment segment2 = TextSegment
.from("LangChain4j integrates with Oracle Database 23ai to store and retrieve vector embeddings.");
Embedding embedding2 = embeddingModel.embed(segment2).content();
embeddingStore.add(embedding2, segment2);
// Create RAG agent with memory using AiServices
OracleRag agent = AiServices.builder(OracleRag.class).chatModel(chatModel).moderationModel(moderationModel)
.contentRetriever(contentRetriever).chatMemory(chatMemory).build();
// Use the RAG agent to answer question 1
String query1 = "What are the features of Oracle Database 23ai?";
System.out.println("Query: " + query1);
String response1 = agent.answer(query1);
System.out.println("Response: " + response1 + "\n");
// Memory confirmation check 1
String memory1 = "What did I ask you in my previous question?";
System.out.println("Query: " + memory1);
String answerRelatedToPreviousDb23aiQuestion = agent.answer(memory1);
System.out.println("Response: " + answerRelatedToPreviousDb23aiQuestion + "\n"); // Oracle Database 23ai features
// Use the RAG agent to answer question 1
String query2 = "Which database LangChain4J integrates with to work with vector embeddings?";
System.out.println("Query: " + query2);
String response2 = agent.answer(query2);
System.out.println("Response: " + response2 + "\n");
// Memory confirmation check 2
String memory2 = "What did I ask you in my previous question?";
System.out.println("Query: " + memory2);
String answerRelatedToPreviousLangChain4JQuestion = agent.answer(memory2);
System.out.println("Response: " + answerRelatedToPreviousLangChain4JQuestion + "\n");
// Auto moderation check
try {
String innapropriateMessage = "I WILL KILL YOU!!!";
System.out.println("Message: " + innapropriateMessage + "\n");
} catch (ModerationException e) {
// Text "I WILL KILL YOU!!!" violates content policy
System.out.println(e.getMessage());
}
}
}Let me walk you through each specific code block of this implementation.
First, we’ll configure a JDBC data source, the OracleEmbeddingStore instance, and the embedding and moderation models.
We start by obtaining a pooled JDBC DataSource, and then initialize the OracleEmbeddingStore, which integrates with the Oracle Database 23ai for storing and querying vector embeddings.
DataSource dataSource = OracleDbUtils.getPooledDataSource();
// Initialize Oracle Embedding Store
EmbeddingStore<TextSegment> embeddingStore = OracleEmbeddingStore.builder().dataSource(dataSource)
.embeddingTable("rag_ai_service_embeddings", CreateOption.CREATE_OR_REPLACE).build();
// Initialize embedding model
EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
OpenAiModerationModel moderationModel = OpenAiModerationModel.builder().apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(TEXT_MODERATION_LATEST).build();We use the quantized All-MiniLM-L6-v2 model for efficient embedding generation. The moderation model from OpenAI ensures compliance with content policies, and it will be linked to the @Moderate annotation marking our Java interface.
Second, we’ll build a content retriever instance, a chat model instance, and a MessageWindowChatMemory instance.
// Initialize content retriever for RAG
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore)
.embeddingModel(embeddingModel).maxResults(10).minScore(0.5).build();
// Initialize Chat Model (OpenAI in this case)
ChatModel chatModel = OpenAiChatModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).modelName(GPT_4_O_MINI)
.build();
// Initialize chat memory for a single user
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder().maxMessages(50).build();Content retrievers help in fetching the relevant segments from the embedding store based on query similarity. In our implementation, the EmbeddingStoreContentRetriever uses the embedding model and store to retrieve up to 10 relevant results with a minimum similarity score of 0.5.
We pair this with the GPT-4o-mini model from OpenAI as the chat model for generating responses. Also, chat memory instance MessageWindowChatMemory keeps track of the last 50 messages, enabling context-aware conversations.
Then, the next code block will add sample data to the vector embedding store, which is ultimately the Oracle Database 23ai. So, let’s populate the store with sample segments about Oracle Database 23ai and LangChain4j.
// Add sample data to the embedding store
TextSegment segment1 = TextSegment
.from("Oracle Database 23ai introduces AI Vector Search for efficient similarity searches.");
Embedding embedding1 = embeddingModel.embed(segment1).content();
embeddingStore.add(embedding1, segment1);
TextSegment segment2 = TextSegment
.from("LangChain4j integrates with Oracle Database 23ai to store and retrieve vector embeddings.");
Embedding embedding2 = embeddingModel.embed(segment2).content();
embeddingStore.add(embedding2, segment2);The code above will create the vector embeddings, then store them. With that, we’ll be able to execute similarity searches later as expected.
Now, the main code block of our scenario. We’ll create our RAG service by using the AiServices.builder() method to instantiate a service instance.
// Create RAG agent with memory using AiServices
OracleRag agent = AiServices.builder(OracleRag.class).chatModel(chatModel).moderationModel(moderationModel)
.contentRetriever(contentRetriever).chatMemory(chatMemory).build();Besides, note that this builder wires everything together in a fluent interface style, as we also have a method call to configure the chat model for generation, moderation for safety, content retriever for RAG, and memory for persistence. AI Services handles the orchestration, invoking retrieval, moderation, and response generation transparently and automatically.
Finally, we can now use the RAG service to perform the similarity searches that comprise RAG, memory, and moderation, all together in action.
// Use the RAG agent to answer question 1
String query1 = "What are the features of Oracle Database 23ai?";
System.out.println("Query: " + query1);
String response1 = agent.answer(query1);
System.out.println("Response: " + response1 + "\n");
// Memory confirmation check 1
String memory1 = "What did I ask you in my previous question?";
System.out.println("Query: " + memory1);
String answerRelatedToPreviousDb23aiQuestion = agent.answer(memory1);
System.out.println("Response: " + answerRelatedToPreviousDb23aiQuestion + "\n"); // Oracle Database 23ai features
// Use the RAG agent to answer question 1
String query2 = "Which database LangChain4J integrates with to work with vector embeddings?";
System.out.println("Query: " + query2);
String response2 = agent.answer(query2);
System.out.println("Response: " + response2 + "\n");
// Memory confirmation check 2
String memory2 = "What did I ask you in my previous question?";
System.out.println("Query: " + memory2);
String answerRelatedToPreviousLangChain4JQuestion = agent.answer(memory2);
System.out.println("Response: " + answerRelatedToPreviousLangChain4JQuestion + "\n");
// Auto moderation check
try {
String innapropriateMessage = "I WILL KILL YOU!!!";
System.out.println("Message: " + innapropriateMessage + "\n");
agent.answer(innapropriateMessage);
} catch (ModerationException e) {
// Text "I WILL KILL YOU!!!" violates content policy
System.out.println(e.getMessage());
e.printStackTrace();
}Step 3: Run the sample application
Now you can use your preferred IDE to compile and run the code.
We’ll see the results of our first similarity search:
Next, we’ll have a confirmation that memory is working as expected:
Then, our second similarity search:
And again, another confirmation that memory is implemented as desired:
And finally, we’ll see the message that results from a moderation exception:
To summarize it, as you can see, using the new AI Services high-level API in LangChain4J, we’ve created a RAG service that:
- Stores embeddings in Oracle Database
- Retrieves contextually relevant data
- Remembers conversation history
- Applies content moderation automatically
Last but not least, here’s the OracleDBUtils.java implementation. Note that I have externalized the JDBC connection details as environment variables, so I do not have to disclose my connection details.
package com.oracle.dev.jdbc.langchain4j;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;
public class OracleDbUtils {
// JDBC connection details
private final static String URL = "jdbc:oracle:thin:@localhost:1521/FREEPDB1";
private final static String USERNAME = System.getenv("DB_23AI_USERNAME");
private final static String PASSWORD = System.getenv("DB_23AI_PASSWORD");
public static Connection getConnectionFromPooledDataSource() throws SQLException {
// Create pool-enabled data source instance
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
// set connection properties on the data source
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL(URL);
pds.setUser(USERNAME);
pds.setPassword(PASSWORD);
// Configure pool properties with a Properties instance
Properties prop = new Properties();
prop.setProperty("oracle.jdbc.vectorDefaultGetObjectType", "String");
pds.setConnectionProperties(prop);
// Override any pool properties directly
pds.setInitialPoolSize(10);
// Get a database connection from the pool-enabled data source
Connection conn = pds.getConnection();
return conn;
}
public static DataSource getPooledDataSource() throws SQLException {
// Create pool-enabled data source instance
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
// set connection properties on the data source
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL(URL);
pds.setUser(USERNAME);
pds.setPassword(PASSWORD);
// Configure pool properties with a Properties instance
Properties prop = new Properties();
prop.setProperty("oracle.jdbc.vectorDefaultGetObjectType", "String");
pds.setConnectionProperties(prop);
// Override any pool properties directly
pds.setInitialPoolSize(10);
return pds;
}
}If you need to configure a running instance of Oracle Database 23ai, my previous blog posts listed below provide all details about it.
- Getting started with Oracle’s contribution to LangChain4J
- Retrieval-Augmented Generation (RAG) with LangChain4j, OpenAI, and the Oracle Database 23ai
You can also get a Maven project with a complete code sample on GitHub.
Wrapping it up
That’s it! Our simple service implementation illustrated how easy it is to use the new AI Services API in LangChain4J and its encapsulated capabilities to address several requirements, such as implementing RAG (Retrieval-Augmented Generation) to retrieve context for more accurate and meaningful answers, remember prior questions via chat memory, and throw a ModerationException to flag inappropriate content — all powered by the new high-level AI Services API!
Just to mention, LangChain4j has also introduced a new module langchain4j-agentic tailored specifically for agentic development.
It’s an agentic suite with abstractions and utilities purpose-built in LangChain4j 1.3.0, enabling more sophisticated multi-agent workflows where agents can delegate tasks, share context, and collaborate.
My next blog post about LangChain4J will present the new langchain4j-agentic module.
As one last tip, I invite you to check out the new Database Navigator tool published at JetBrains Marketplace. In a nutshell, it features a robust SQL and PL/SQL editor with advanced capabilities for managing database connections, executing scripts, editing database objects, and modifying data and code with IntelliJ!
I will soon post more examples of scenarios involving LangChain4J, the OracleEmbeddingStore, GraalVM, Micronaut, Quarkus, and other GenAI / Agentic AI-related topics!
We hope you liked this blog post. Stay tuned!
References
LangChain4J — Oracle Embedding Store
Maven — Oracle Embedding Store (langchain4j-oracle)
Oracle AI Vector Search User’s Guide
Oracle JDBC Driver 23ai — Maven Central
Oracle AI Vector Search Technical Architecture
Oracle Database Free Release 23ai — Container Image
Oracle® Database JDBC Java API Reference, Release 23ai
Database Navigator — JetBrains Marketplace
Quickstart: Connect to Oracle Database 23ai using IntelliJ IDEA
Developers Guide For Oracle JDBC on Maven Central
Develop Java applications with Oracle Database
Oracle Developers and Oracle OCI Free Tier
Join our Oracle Developers channel on Slack to discuss Java, GenAI, Agentic AI, JDK, JDBC, GraalVM, Micronaut, Spring Boot, Helidon, Quarkus, Reactive Streams, Cloud, DevOps, SRE, IaC, and other topics!
Build, test, and deploy your applications on Oracle Cloud — for free! Get access to OCI Cloud Free Tier!

