Sitemap
Oracle Developers

Aggregation of articles from Oracle engineers, Oracle ACEs, and Java Champions on all things Oracle technology. The views expressed are those of the authors and not necessarily of Oracle.

Unleash the Power of AI Agents with Java: Building Intelligent Applications with Quarkus, LangChain4J, and Oracle AI Database

10 min readDec 12, 2025

--

Press enter or click to view image in full size
Quarkus, LangChain4J, and the Oracle AI Database 26ai

by Juarez Junior

Introduction

In this blog, we’ll build a Quarkus-based AI support agent that handles customer support for Oracle database error codes. Using LangChain4J, it processes natural language prompts, searches a knowledge base stored in Oracle AI Database 26ai via Retrieval-Augmented Generation (RAG), and automatically sends emails with error descriptions, root causes, and proposed solutions.

The agent intelligently understands prompts such as “A customer reported ORA-00900” and responds by calling different tools to achieve its goals.

By reporting different error messages in natural language, you’ll see that our support agent may change its behavior more probabilistically - an agentic approach to business solutions instead of deterministic ones based on strict business logic.

I assume you understand what an AI Agent is about. Otherwise, check this blog post — What Are AI Agents?

So without further ado, let’s get started!

Prerequisites

Step 1: Configure your Java application

Configure your Maven pom.xml file with the following dependencies: Oracle JDBC driver, Oracle LangChain4J integration, Quarkus AI Services, LangChain4J Agentic Framework, Quarkus LangChain4J integration, UCP, and other dependencies.

Using Maven

  <properties>
<compiler-plugin.version>3.14.1</compiler-plugin.version>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.29.4</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.2.5</surefire-plugin.version>
<jdbc.version>23.26.0.0.0</jdbc.version>
</properties>

<dependencies>
<!-- Oracle AI Database 26ai JDBC / UCP -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc17</artifactId>
<version>${jdbc.version}</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ucp17</artifactId>
<version>${jdbc.version}</version>
</dependency>
<!-- Oracle - LangChain4j Integration -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-oracle</artifactId>
<version>1.9.1-beta17</version>
</dependency>
<!-- Quarkus -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-oracle</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mailer</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkiverse.mailpit</groupId>
<artifactId>quarkus-mailpit</artifactId>
<version>1.7.0</version>
</dependency>
<!-- Quarkus LangChain4j OpenAI -->
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-openai</artifactId>
</dependency>
<!-- Quarkus Qute -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-qute</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.qute.web</groupId>
<artifactId>quarkus-qute-web</artifactId>
</dependency>
<!-- LangChain4j -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.3.0</version>
</dependency>
<!-- LangChain4j Agentic -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic</artifactId>
<version>1.3.0-beta9</version>
</dependency>
</dependencies>

Next, configure the application.properties file, as shown below. It includes properties for OpenAI and its API key, as well as the chat and embedding models.

Besides, note the JDBC data source properties to support using Oracle AI Database 26ai as a vector store.

Also, the Quarkus mock mailer component is enabled for development, so emails will be logged only, instead of being effectively sent.

quarkus.langchain4j.timeout=60s
quarkus.mailer.mock=true

# OpenAI
quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY}
quarkus.langchain4j.openai.chat-model.model-name=gpt-4o
quarkus.langchain4j.openai.embedding-model.model-name=text-embedding-3-small

# Oracle Database JDBC
quarkus.datasource.db-kind=oracle
quarkus.datasource.jdbc.url=jdbc:oracle:thin:@localhost:1521/FREEPDB1
quarkus.datasource.username=${DB_USERNAME}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.datasource.jdbc.driver=oracle.jdbc.OracleDriver
quarkus.datasource.jdbc.initial-size=10
quarkus.datasource.jdbc.min-size=10
quarkus.datasource.jdbc.max-size=50

# Optional vector settings
quarkus.datasource.jdbc.additional-jdbc-properties.vectorDefaultGetObjectType=String

Step 2: Understand the agent architecture components

The agent is implemented with the following core components:

Let’s have a look at each of them.

TechSupportAgentResource.java

No tricks here, this is just a trivial REST endpoint, exposing an integration point that accepts HTTP POST requests.

package com.oracle.dev.quarkus.langchain4j.agent;

import java.util.List;

import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/agent")
public class TechSupportAgentResource {

@Inject
TechSupportAgent agent;

@Inject
OracleErrorCodeLookup exactCodeLookup;

@POST
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String agent(String prompt) {
return agent.chat(withAuthoritativeErrorDescriptions(prompt));
}

private String withAuthoritativeErrorDescriptions(String prompt) {
List<String> exactMatches = exactCodeLookup.findExactMatches(prompt);
if (exactMatches.isEmpty()) {
return prompt;
}
return prompt + "\n\nAuthoritative Oracle Database knowledge-base entries retrieved for this request "
+ "(use these descriptions; do not state that they are unavailable):\n" + String.join("\n", exactMatches);
}
}

RagInitializationService.java

On startup, RagInitializationService.java automatically loads the Oracle error code descriptions from our RAG raw data source, a simple markdown file (src/main/resources/rag/oracle-error-codes-descriptions.md). This happens once when the application starts. You will see a log message: Loaded 35 Oracle AI Database 26ai error code entries into the vector store.

It splits the file into individual documents, generates embeddings using OpenAI’s text-embedding-3-small model, and stores them in an Oracle AI Database 26ai table (rag_agent_embeddings), as shown below.

  @PostConstruct
public void initialize() throws SQLException {
embeddingStore = OracleEmbeddingStore.builder().dataSource(dataSource)
.embeddingTable("rag_agent_embeddings", CreateOption.CREATE_IF_NOT_EXISTS).build();
ingestOracleErrorCodes();
}

Below is the full class.

package com.oracle.dev.quarkus.langchain4j.agent;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.sql.DataSource;

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.oracle.CreateOption;
import dev.langchain4j.store.embedding.oracle.OracleEmbeddingStore;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import io.quarkus.runtime.Startup;

@ApplicationScoped
@Startup
public class RagInitializationService {

@Inject
EmbeddingModel embeddingModel;

@Inject
DataSource dataSource;

private EmbeddingStore<TextSegment> embeddingStore;

@PostConstruct
public void initialize() throws SQLException {
embeddingStore = OracleEmbeddingStore.builder().dataSource(dataSource)
.embeddingTable("rag_agent_embeddings", CreateOption.CREATE_IF_NOT_EXISTS).build();
ingestOracleErrorCodes();
}

public EmbeddingStore<TextSegment> getEmbeddingStore() {
return embeddingStore;
}

public EmbeddingModel getEmbeddingModel() {
return embeddingModel;
}

private void ingestOracleErrorCodes() {
if (vectorTableHasRows()) {
System.out.println("--- Vector store already populated; skipping Oracle error code ingestion. ---");
return;
}

List<Document> documents = new ArrayList<>();

try (var inputStream = RagInitializationService.class.getClassLoader()
.getResourceAsStream("rag/oracle-error-codes-descriptions.md")) {

if (inputStream == null) {
throw new IllegalStateException(
"Resource rag/oracle-error-codes-descriptions.md not found in src/main/resources/rag/");
}

try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
documents = reader.lines().filter(line -> !line.trim().isEmpty()).map(Document::from)
.collect(Collectors.toList());
}

} catch (IOException e) {
throw new RuntimeException("Failed to load oracle-error-codes-descriptions.md", e);
}

if (embeddingModel != null) {
for (Document doc : documents) {
Embedding embedding = embeddingModel.embed(doc.text()).content();
embeddingStore.add(embedding, TextSegment.from(doc.text()));
}
}

System.out.println(
"--- Loaded " + documents.size() + " Oracle AI Database 26ai error code entries into the vector store. ---");
}

private boolean vectorTableHasRows() {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement();
var resultSet = statement.executeQuery("SELECT COUNT(*) FROM rag_agent_embeddings")) {
return resultSet.next() && resultSet.getLong(1) > 0;
} catch (SQLException e) {
return false;
}
}
}

TechSupportAgent.java

TechSupportAgent.java is a Java interface that defines our agent’s contract and processes user prompts, and then invokes the available tools based on the needs inferred from the message, as it’s annotated with @RegisterAiService. The tools run autonomously per the agent’s ReAct loop.

In a nutshell, a ReAct agent applies the “reasoning and acting” framework, blending chain-of-thought reasoning with tool use. This approach empowers large language models to tackle complex tasks and make decisions in agentic workflows.

It also tells Quarkus to generate an AI-backed implementation where the chat(String message) method, marked with the @UserMessage annotation, serves as the agent’s entry point.

package com.oracle.dev.quarkus.langchain4j.agent;

import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.SystemMessage;
import io.quarkiverse.langchain4j.RegisterAiService;

@RegisterAiService(tools = { EmailTool.class, OracleDatabaseTool.class, LoggingTool.class })
public interface TechSupportAgent {

@SystemMessage("""
You are an Oracle Database customer-support agent. When the user message includes
authoritative Oracle Database knowledge-base entries, treat them as the source of truth:
include the matching error description in your answer and in any email or log you create.
Do not claim that an error code is unavailable when an authoritative entry is present.
""")
String chat(@UserMessage String message);

}

Next, we have three simple tools to illustrate how they can be autonomously selected and dynamically invoked.

EmailTool.java

This tool mocks sending an email via Quarkus Mailer.

package com.oracle.dev.quarkus.langchain4j.agent;

import dev.langchain4j.agent.tool.Tool;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class EmailTool {

@Inject
Mailer mailer;

@Tool("Sends an email to a specified recipient using the error code as subject and the respective error description as the email text body")
public String sendEmail(String recipient, String subject, String body) {
mailer.send(Mail.withText(recipient, subject, body));
return jakarta.ws.rs.core.Response.Status.OK.name();
}
}

OracleDatabaseTool.java

This tool uses the Oracle LangChain4J extension and the underlying Oracle AI Vector Search mechanisms to perform semantic searches in the Oracle AI Database 26ai (vector store).

package com.oracle.dev.quarkus.langchain4j.agent;

import java.util.List;

import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.Content;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.query.Query;
import dev.langchain4j.store.embedding.EmbeddingStore;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class OracleDatabaseTool {

private static final String NO_RESULTS = "--- No relevant information found in the Oracle Database knowledge base. ---";

private ContentRetriever retriever;

@Inject
RagInitializationService ragInitializationService;

@Inject
OracleErrorCodeLookup exactCodeLookup;

@jakarta.annotation.PostConstruct
public void init() {

EmbeddingStore<TextSegment> embeddingStore = ragInitializationService.getEmbeddingStore();
EmbeddingModel embeddingModel = ragInitializationService.getEmbeddingModel();

if (embeddingStore == null || embeddingModel == null) {
throw new IllegalStateException("RagInitializationService not properly initialized");
}

this.retriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore)
.embeddingModel(embeddingModel).maxResults(40).minScore(0.3).build();
}

@Tool("Searches the Oracle Database error codes knowledge base to answer a customer's question about error code descriptions")
public String searchKnowledgeBase(String query) {
List<String> exactMatches = exactCodeLookup.findExactMatches(query);
if (!exactMatches.isEmpty()) {
return String.join("\n", exactMatches);
}

List<Content> contents = retriever.retrieve(Query.from(query));
if (contents == null || contents.isEmpty()) {
return NO_RESULTS;
}

var ordered = new java.util.LinkedHashSet<String>();

for (Content content : contents) {
TextSegment segment = content.textSegment();
if (segment == null) {
continue;
}
String text = segment.text();
ordered.add(text);
}

if (ordered.isEmpty()) {
return NO_RESULTS;
}
return String.join("\n", ordered);
}

}

LoggingTool.java

This tool only writes messages to the console, so it doesn’t do anything special. However, it will support us in demonstrating different behaviours, depending on the natural language message received from the user.

You can try it by sending messages that do not mention email messages, and you will see that the email tool won’t be included in the ReAct loop.

package com.oracle.dev.quarkus.langchain4j.agent;

import dev.langchain4j.agent.tool.Tool;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class LoggingTool {

@Tool("Given and error code, only if an email message has not been sent to customer@buildstuff.events, logs the corresponding error description as a log message")
public void logErrorDescription(String logMessage) {
System.out.println("--- Logging message: " + logMessage + "---");
}

}

Step 3: Build and run the agent application

Build the application.

mvn clean package

Then run it in production mode (or use mvnw quarkus:dev for dev mode)

java -jar target/quarkus-app/quarkus-run.jar

As soon as it starts up, you will see logs indicating Quarkus startup and the HTTP server running on port 8080. You will see messages related to the RAG initialization and vector embeddings.

Press enter or click to view image in full size
Agent app started successfully

Open a web browser and navigate to http://localhost:8080.

You will see the web interface below. Note that now you must enter your prompt in natural language, or select one of the Quick Starts options below the Ask the agent button to reuse an existing prompt before submitting.

Press enter or click to view image in full size
A customer reported an Oracle Database error code ORA-00054. Send an email to customer@cybernetics.com with the error description, possible root causes, and solutions.

If you have a database client, connect to your database instance using your credentials and JDBC URL, and inspect the rag_agent_embeddings in your target DB schema. It should have 35 records in total, as expected.

Press enter or click to view image in full size
The ag_agent_embeddings table with the vector embeddings

Now I advise you to interact with the agent using natural language and submit as many messages as you want. I will leave that as an exercise for you to analyse the different behaviour. Nevertheless, below is a sample output message.

Press enter or click to view image in full size
Agent answer message

If you need help configuring a running instance of Oracle AI Database 26ai, my previous blog posts listed below provide all the details.

You can also get a Maven project with a complete code sample on GitHub.

Wrapping it up

That’s it! You now have a fully functional AI agent that understands natural language, retrieves information from Oracle AI Database 26ai using vector search, and takes action by using different tools depending on your prompt message. We explored Quarkus and LangChain4J, and concluded that they provide a rich set of out-of-the-box Java components that allow you to build great agentic AI applications.

Combine that with the Oracle AI Database 26ai, the OracleEmbeddingStore, and Oracle AI Vector Search to easily implement RAG solutions, enabling your agents to use a mission-critical, enterprise vector store to build AI applications.

As you build on this foundation, you can extend your agents with richer knowledge bases, more sophisticated tools, and additional capabilities like multi-agent collaboration or Model Context Protocol (MCP) integration.

Last, I invite you to check out the new Database Navigator tool available at the 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, Spring AI, GraalVM, Micronaut, Quarkus, and other GenAI / Agentic AI-related topics!

That’s it! I hope you liked this blog post. Thanks for reading!

References

LangChain4J

LangChain4J — Javadoc

LangChain4J — Oracle Embedding Store

Maven — Oracle Embedding Store (langchain4j-oracle)

Quarkus

Quarkus LangChain4J

Oracle AI Database 26ai

Oracle AI Database 26ai Free Container Image

Oracle JDBC Driver

Oracle® AI Database JDBC Java API Reference, Release 26ai

Creating AI Agents with LangChain4J, Model Context Protocol, Oracle SQLcl MCP Server, and the Oracle AI Database 26ai

The LangChain4J AI Services API — Creating RAG services with Oracle AI Vector Search, OracleEmbeddingStore, and the Oracle Database 23ai

The LangChain4J Agentic AI API — Creating RAG Agents with Oracle AI Vector Search, OracleEmbeddingStore, and the Oracle AI Database 26ai

RAG the Easy Way with Oracle AI Vector Search, LangChain4J, OracleEmbeddingStore, and Oracle Database 23ai

Getting started with Oracle’s contribution to LangChain4J

Retrieval-Augmented Generation (RAG) with LangChain4J, OpenAI, and the Oracle Database 23ai

Oracle AI Vector Search User’s Guide

Oracle AI Vector Search Technical Architecture

Database Navigator — JetBrains Marketplace

Developer’s Guide For Oracle JDBC on Maven Central

Oracle JDBC Development Center

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 the OCI Cloud Free Tier!

--

--

Juarez Junior
Juarez Junior

Written by Juarez Junior

Senior Principal Developer Evangelist @ Oracle ☕️🐍🥑 https://linktr.ee/juarezjunior

Oracle Developers
Oracle Developers

Published in Oracle Developers

Aggregation of articles from Oracle engineers, Oracle ACEs, and Java Champions on all things Oracle technology. The views expressed are those of the authors and not necessarily of Oracle.