Partner – Orkes – NPI EA (cat=Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag=Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Partner – LambdaTest – NPI EA (cat=Testing)
announcement - icon

Browser testing is essential if you have a website or web applications that users interact with. Manual testing can be very helpful to an extent, but given the multiple browsers available, not to mention versions and operating system, testing everything manually becomes time-consuming and repetitive.

To help automate this process, Selenium is a popular choice for developers, as an open-source tool with a large and active community. What's more, we can further scale our automation testing by running on theLambdaTest cloud-based testing platform.

Read more through our step-by-step tutorial on how to set up Selenium tests with Java and run them on LambdaTest:

>> Automated Browser Testing With Selenium

Partner – Orkes – NPI EA (cat=Java)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

1. Overview

With traditional databases, we typically rely on exact keyword or basic pattern matching to implement our search functionality. While sufficient for simple applications, this approach fails to understand the meaning and context behind natural language queries fully.

Vector stores address this limitation by storing data as numeric vectors that capture their meaning. Similar words are clustered together, which allows for similarity search, where the database returns relevant results even if they don’t contain the exact keywords used in the query.

Oracle Database 23ai integrates this vector store capability into its existing ecosystem, allowing us to build AI applications without needing a separate vector store. Using the same database, we can create solutions that use both traditional structured data management and vector similarity search.

In this tutorial, we’ll explore integrating the Oracle vector database with Spring AI. We’ll implement native similarity search to find semantically related content. Then, we’ll build upon this capability to implement a Retrieval-Augmented Generation (RAG) chatbot.

2. Setting up the Project

Before we dive into the implementation, we’ll need to include the necessary dependencies and configure our application correctly.

2.1. Dependencies

Let’s start by adding the necessary dependencies to our project’s pom.xml file:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-oracle</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-advisors-vector-store</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>1.0.0</version>
</dependency>

The Oracle vector store starter dependency enables us to establish a connection with the Oracle vector database and interact with it. Additionally, we import the vector store advisors dependency for our RAG implementation.

Finally, we import Spring AI’s OpenAI starter dependency, which we’ll use to interact with the chat completion and embedding models.

Given that we’re using multiple Spring AI starters in our project, let’s also include the Spring AI Bill of Materials (BOM) in our pom.xml:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

With this addition, we can now remove the version tag from our starter dependencies. The BOM eliminates the risk of version conflicts and ensures the Spring AI dependencies are compatible with each other.

2.2. Configuring AI Models and Vector Store Properties

To convert our text data into vectors that the Oracle vector database can store and search, we’ll need an embedding model. Additionally, for our RAG chatbot, we’ll also need a chat completion model.

For our demonstration, we’ll use the models provided by OpenAI. Let’s configure the OpenAI API key and the models in our application.yaml file:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      embedding:
        options:
          model: text-embedding-3-large
      chat:
        options:
          model: gpt-4o

We use the ${} property placeholder to load the value of our API key from an environment variable.

Additionally, we specify text-embedding-3-large and gpt-4o as our embedding and chat completion models, respectively. On configuring these properties, Spring AI automatically creates a bean of type ChatModel, which we’ll use later in the tutorial.

Alternatively, we can use different models, as the specific AI model or provider is irrelevant for this demonstration.

Next, to store and search data in our vector database, we must first initialize its schema:

spring:
  ai:
    vectorstore:
      oracle:
        initialize-schema: true

Here, we set the spring.ai.vectorstore.oracle.initialize-schema to true.

This instructs Spring AI to create the necessary default vector store schema automatically on application startup, which is convenient for local development and testing. However, for production applications, we should define the schema manually using a database migration tool, like Flyway.

3. Populating Oracle Vector Database

With our configurations in place, let’s set up a workflow to populate our Oracle vector database with some sample data during application startup.

3.1. Fetching Quote Records From an External API

For our demonstration, we’ll use the Breaking Bad Quotes API to fetch quotes.

Let’s create a QuoteFetcher utility class for this:

class QuoteFetcher {
    private static final String BASE_URL = "https://api.breakingbadquotes.xyz/v1/quotes/";
    private static final int DEFAULT_COUNT = 150;

    static List<Quote> fetch() {
        return fetch(DEFAULT_COUNT);
    }

    static List<Quote> fetch(int count) {
        return RestClient
          .create()
          .get()
          .uri(URI.create(BASE_URL + count))
          .retrieve()
          .body(new ParameterizedTypeReference<>() {});
    }
}

record Quote(String quote, String author) {
}

Using RestClient, we invoke the external API with the default count of 150 and use ParameterizedTypeReference to deserialize the API response to a list of Quote records.

3.2. Storing Documents in Vector Database

Now, to populate our Oracle vector database with quotes during application startup, we’ll create a VectorStoreInitializer class that implements the ApplicationRunner interface:

@Component
class VectorStoreInitializer implements ApplicationRunner {
    private final VectorStore vectorStore;

    // standard constructor

    @Override
    public void run(ApplicationArguments args) {
        List<Document> documents = QuoteFetcher
          .fetch()
          .stream()
          .map(quote -> {
              Map<String, Object> metadata = Map.of("author", quote.author());
              return new Document(quote.quote(), metadata);
          })
          .toList();
        vectorStore.add(documents);
    }
}

In our VectorStoreInitializer class, we autowire an instance of VectorStore, which Spring AI automatically creates for us.

Inside the run() method, we use our QuoteFetcher utility class to retrieve a list of Quote records. Then, we map each quote into a Document and configure the author field as metadata.

Finally, we store all the documents in our database. When we invoke the add() method, Spring AI automatically converts our plaintext content into a vector representation before storing it in the database.

4. Setting up Local Test Environment With Testcontainers

To facilitate local development and testing, we’ll use Testcontainers to set up the Oracle vector database, the prerequisite for which is an active Docker instance.

First, let’s add the necessary test dependencies to our pom.xml:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>oracle-free</artifactId>
    <scope>test</scope>
</dependency>

We import the Spring AI Testcontainers dependency for Spring Boot and the Oracle Database module of Testcontainers.

These dependencies provide the necessary classes to spin up an ephemeral Docker instance for the Oracle vector database.

Next, let’s create a @TestConfiguration class to define our Testcontainers bean:

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
    @Bean
    @ServiceConnection
    OracleContainer oracleContainer() {
        return new OracleContainer("gvenzl/oracle-free:23-slim");
    }
}

We specify the latest stable slim version of the Oracle database image when creating the OracleContainer bean.

Additionally, we annotate our bean method with @ServiceConnection. This dynamically registers all the datasource properties required to set up a connection with the Docker container.

Now, we can use this configuration in our integration tests by annotating our test classes with the @Import(TestcontainersConfiguration.class) annotation.

5. Performing Similarity Searches

Now that we’ve set up our local testing environment and populated our Oracle vector database with Breaking Bad quotes, let’s explore how to perform similarity searches.

Let’s start by performing basic similarity search operations to find quotes matching various Breaking Bad themes:

private static final int MAX_RESULTS = 5;

@Autowired
private VectorStore vectorStore;

@ParameterizedTest
@ValueSource(strings = { "Sarcasm", "Regret", "Violence and Threats", "Greed, Power, and Money" })
void whenSearchingBreakingBadTheme_thenRelevantQuotesReturned(String theme) {
    SearchRequest searchRequest = SearchRequest
      .builder()
      .query(theme)
      .topK(MAX_RESULTS)
      .build();

    List<Document> documents = vectorStore.similaritySearch(searchRequest);

    assertThat(documents)
      .hasSizeGreaterThan(0)
      .hasSizeLessThanOrEqualTo(MAX_RESULTS)
      .allSatisfy(document -> {
          assertThat(document.getText())
            .isNotBlank();
          assertThat(String.valueOf(document.getMetadata().get("author")))
            .isNotBlank();
      });
}

Here, we pass the main themes from the Breaking Bad series to our test method using @ValueSource. Then, we create a SearchRequest object with the theme as the query. Additionally, we limit results to the top five most similar quotes by passing MAX_RESULTS to the topK() method.

Next, we call the similaritySearch() method of our vectorStore bean, with our searchRequest. Similar to the add() method of the VectorStore, Spring AI converts our query to its vector representation before querying the database.

The returned documents will contain quotes that are semantically related to the given theme, even if they don’t include the exact keyword.

5.2. Filtering Using Metadata

In addition to performing basic similarity searches, the Oracle vector database also supports filtering search results based on the saved metadata. This is useful when we need to narrow down our search and perform semantic searches within a subset of data.

Let’s again search for quotes related to a given theme, but filter them by a specific author:

@ParameterizedTest
@CsvSource({
    "Walter White, Pride",
    "Walter White, Control",
    "Jesse Pinkman, Abuse and foul language",
    "Mike Ehrmantraut, Wisdom",
    "Saul Goodman, Law"
})
void whenSearchingCharacterTheme_thenRelevantQuotesReturned(String author, String theme) {
    SearchRequest searchRequest = SearchRequest
      .builder()
      .query(theme)
      .topK(MAX_RESULTS)
      .filterExpression(String.format("author == '%s'", author))
      .build();

    List<Document> documents = vectorStore.similaritySearch(searchRequest);

    assertThat(documents)
      .hasSizeGreaterThan(0)
      .hasSizeLessThanOrEqualTo(MAX_RESULTS)
      .allSatisfy(document -> {
          assertThat(document.getText())
            .isNotBlank();
          assertThat(String.valueOf(document.getMetadata().get("author")))
            .contains(author);
      });
}

Here, we use the @CsvSource annotation to find quotes using various character-theme combinations.

We build our SearchRequest as before, but this time, we use the filterExpression() method to restrict results to quotes from a specific author.

6. Building a RAG Chatbot

While native similarity search is powerful on its own, we can build upon this capability to create an intelligent, context-aware RAG chatbot.

6.1. Defining a Prompt Template

To better guide the LLM’s behaviour, we’ll define a custom prompt template. Let’s create a new prompt-template.st file in the src/main/resources directory:

You are a chatbot built for analyzing quotes from the 'Breaking Bad' television series.
Given the quotes in the CONTEXT section, answer the query in the USER_QUESTION section.
The response should follow the guidelines listed in the GUIDELINES section.

CONTEXT:
<question_answer_context>

USER_QUESTION:
<query>

GUIDELINES:
- Base your answer solely on the information found in the provided quotes.
- Provide concise, direct answers without mentioning "based on the context" or similar phrases.
- When referencing specific quotes, mention the character who said them.
- If the question cannot be answered using the context, respond with "The provided quotes do not contain information to answer this question."
- If the question is unrelated to the Breaking Bad show or the quotes provided, respond with "This question is outside the scope of the available Breaking Bad quotes."

Here, we clearly define the chatbot’s persona and provide it with a set of guidelines to follow.

In our template, we use two placeholders enclosed in angle brackets. Spring AI will automatically replace the question_answer_context and the query placeholders with the retrieved context from the vector database and the user’s question, respectively.

6.2. Configuring a ChatClient Bean

Next, let’s define a bean of type ChatClient, which acts as the main entry point for interacting with the configured chat completion model:

private static final int MAX_RESULTS = 10;

@Bean
PromptTemplate promptTemplate(
  @Value("classpath:system-prompt.st") Resource promptTemplate) {
    String template = promptTemplate.getContentAsString(StandardCharsets.UTF_8);
    return PromptTemplate
      .builder()
      .renderer(StTemplateRenderer
        .builder()
        .startDelimiterToken('<')
        .endDelimiterToken('>')
        .build())
      .template(template)
      .build();
}

@Bean
ChatClient chatClient(
  ChatModel chatModel,
  VectorStore vectorStore,
  PromptTemplate promptTemplate) {
    return ChatClient
      .builder(chatModel)
      .defaultAdvisors(
        QuestionAnswerAdvisor
          .builder(vectorStore)
          .promptTemplate(promptTemplate)
          .searchRequest(SearchRequest
            .builder()
            .topK(MAX_RESULTS)
            .build())
          .build()
      )
      .build();
}

Here, we first retrieve the contents of our prompt template using the @Value annotation and use it to define a PromptTemplate bean. We also configure it to use the angle brackets as delimiters.

Next, we use the PromptTemplate bean, along with the ChatModel and VectorStore beans, to define our ChatClient bean. We use the defaultAdvisors() method to register a QuestionAnswerAdvisor, which is the component that implements the RAG pattern.

Additionally, within the advisor, we configure a SearchRequest to retrieve the top 10 most relevant quotes. Spring AI will inject them into the prompt template before making the call to the LLM.

6.3. Performing RAG Operation

Now, with the ChatClient bean configured, let’s see how we can interact with it to ask natural language questions:

@Autowired
private ChatClient chatClient;

@ParameterizedTest
@ValueSource(strings = {
    "How does the show portray the mentor-student dynamic?",
    "Which characters in the show portray insecurity through their quotes?",
    "Does the show contain quotes with mature themes inappropriate for young viewers?"
})
void whenQuestionsRelatedToBreakingBadAsked_thenRelevantAnswerReturned(String userQuery) {
    String response = chatClient
      .prompt(userQuery)
      .call()
      .content();

    assertThat(response)
      .isNotBlank();
      .doesNotContain(OUT_OF_SCOPE_MESSAGE, NO_INFORMATION_MESSAGE);
}

Here, when we pass the userQuery to the prompt() method, our configured QuestionAnswerAdvisor performs the RAG workflow behind the scenes. The advisor queries the Oracle vector database for quotes related to the user’s question, injects them into the prompt template, and sends the combined prompt to the configured LLM for a response.

We verify that the response is not blank and doesn’t contain the fallback messages we defined in our template.

7. Conclusion

In this article, we explored how to integrate the Oracle vector database with Spring AI.

We walked through the necessary configurations and implemented two key vector store capabilities: similarity search and RAG. Using Testcontainers, we set up the Oracle vector database, creating a local test environment.

First, we fetched quotes from the Breaking Bad quotes API to populate our vector store during application startup. Then, we implemented a similarity search on the stored data to fetch quotes matching common themes from the series.

Finally, we implemented a RAG chatbot that uses the retrieved quotes from similarity search as context to answer user queries.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

Partner – Orkes – NPI EA (cat = Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag = Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)