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

Course – All Access – NPI EA (cat= Spring)
announcement - icon

All Access is finally out, with all of my Spring courses. Learn JUnit is out as well, and Learn Maven is coming fast. And, of course, quite a bit more affordable. Finally.

>> GET THE COURSE
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.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

The Apache Camel OpenAI component, introduced in Camel 4.17, enables integration of Java applications with OpenAI or OpenAI-compatible LLM (Large Language Model) servers such as Ollama or vLLM. Basically, it uses Camel routes for interaction with LLMs and handles message conversion between Camel Exchanges and OpenAI requests. It uses the OpenAI Java API library under the hood.

In this tutorial, we’ll discuss LLM integration with the Apache Camel OpenAI component and see several examples.

2. Maven Dependencies

We have to add the following Maven dependency to our pom.xml:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-openai</artifactId>
    <version>4.18.0</version>
</dependency>

3. Basic Interaction With an LLM

The component uses the standard Camel URI (Uniform Resource Identifier) format: openai:operation[?options]. The openai part of the URI is fixed.

The operation part corresponds to the URI’s path parameter. Two operations are supported: chat-completion and embeddings. chat-completion, which is of interest to us, generates chat responses using a model. The other operation, embeddings, is useful for creating vector embeddings, for example, in RAG (Retrieval-Augmented Generation) pipelines.

The options part corresponds to the URI’s query parameters. These are generally configuration options such as model choices, generation settings, and authentication parameters. For example, the model option specifies which LLM to use. We can also pass these options using message headers instead of the URI.

3.1. Chat Application

In this section, we’ll examine a simple chat application that interacts with an LLM. The application sends a prompt to the model and displays the model’s response.

Let’s start by using the Apache Camel’s Main class to configure, run, and handle the lifecycle of a standalone Apache Camel application:

Main main = new Main();
main.configure()
  .addRoutesBuilder(new ChatRoute());
main.start();

The addRoutesBuilder() method registers a custom routing class, ChatRoute, that implements the routing rules. The routing logic includes how Apache Camel takes a prompt, wraps it in an HTTP call to an LLM, and processes the response. The start() method of the Main class starts the runtime, instantiates the routes, and begins processing prompts.

Then, let’s send a message to an endpoint using the ProducerTemplate class:

ProducerTemplate template = main.getCamelContext()
    .createProducerTemplate();
String aiResponse = template.requestBody("direct:startChat", userPrompt, String.class);

We use the requestBody() method of ProducerTemplate to pass the prompt to the route. The first argument, “direct:startChat”, is the endpoint that the LLM exposes. direct: is an Apache Camel component providing synchronous invocation of routes. startChat is the name we give to the endpoint.

Our application uses this endpoint to communicate with the LLM. We pass the second argument, userPrompt, to the LLM and store its response in the variable aiResponse. The third argument, String.class, tells Apache Camel that we expect the response to be of type String. Therefore, the type of aiResponse is String.

3.2. The Routing Class

Next, let’s discuss the routing class. We extend the ChatRoute class from the RouteBuilder class to create routing rules using the Java DSL (Domain-Specific Language):

public class ChatRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {

        onException(Exception.class).handled(true)
          .log(org.apache.camel.LoggingLevel.INFO, "OpenAI Error: ${exception.message}")
          .setBody(simple("OpenAI request failed."));

        from("direct:startChat")
          .setHeader(
            OpenAIConstants.SYSTEM_MESSAGE,
            constant("You are a helpful, brief IT support assistant."))
          .setHeader(OpenAIConstants.TEMPERATURE, constant(0.1))
          .setHeader(OpenAIConstants.MODEL, constant("gpt-4o-mini"))
          .to("openai:chat-completion")
          .log("AI Response: ${body}");
    }
}

We override the configure() method, which acts as the entry point. from(“direct:startChat”) defines a synchronous endpoint named startChat. As we saw in the previous subsection, applications can send user prompts to this endpoint by calling template.requestBody(“direct:startChat”, userPrompt, String.class).

The three setHeader() calls attach OpenAI-specific message headers by injecting the following key-value pairs:

  • SYSTEM_MESSAGE sets the system prompt to “You are a helpful, brief IT support assistant.” and therefore defines the behavior and tone of the model.
  • MODEL picks gpt-4o-mini as the target LLM.
  • TEMPERATURE sets the sampling temperature to 1, thereby making the model more deterministic.

to(“openai:chat-completion”) is the actual call to the OpenAI component. It extracts the message body (i.e., the user prompt) and the headers, performs the OpenAI chat completion request, and waits for the response.

Instead of using message headers above, we can also use the Camel URI. For example, we can set the model and temperature options using the URI:

.to("openai:chat-completion?model=gpt-4o-mini&temperature=0.1")

Finally, log(“AI Response: ${body}”) prints the model’s response.

We must authenticate our application while making requests to OpenAI’s developer platform. One method for authentication is to set the confidential OpenAI API key to the OPENAI_API_KEY environment variable from the terminal we’ll run the application in Linux:

$ export OPENAI_API_KEY=<our_confidential_actual_key_here>

Let’s see the response of the LLM when we send the prompt, “Give me information about Baeldung”:

AI Response: Baeldung is a popular online platform that provides tutorials and articles primarily focused on Java, 
Spring Framework, and related technologies.
...
The content is well-structured and often includes practical examples, 
making it a valuable resource for both beginners and experienced developers.

The output shows that we received a successful response to our prompt. We abbreviated the response as it’s long.

4. Using Other OpenAI-Compatible Endpoints

The Apache Camel OpenAI component allows us to switch easily to other OpenAI-compatible endpoints using the baseUrl option. The endpoint can point to local or third-party providers. For example, we just need to change the router code as follows to use a local Ollama server:

.setHeader(OpenAIConstants.MODEL, constant("qwen3.5:9b"))
.to("openai:chat-completion?baseUrl=http://localhost:11434/v1")

The default value of baseUrl is https://api.openai.com/v1. We used the default OpenAI endpoint in the previous section. Therefore, we didn’t specify it explicitly. However, by setting baseUrl to http://localhost:11434/v1, we send the requests to the local Ollama server instead of OpenAI’s servers.

We also set the model to qwen3.5:9b in our example. Before sending prompts to the model, the model must have been run locally using the ollama run command. We don’t need to use a real API key while using Ollama locally. But an API key is still required. So we can set it to a dummy value for compatibility with OpenAI. But third-party providers over the cloud, like Ollama’s cloud service, require a real API key.

5. Keeping Conversational Memory

By default, conversational memory isn’t retained by the OpenAI component; i.e., OpenAI doesn’t remember the responses to previous prompts. For example, if we tell an LLM our name in one prompt and then ask for it in the next prompt, it will usually respond that it doesn’t know our name.

To maintain conversational memory with the OpenAI component, we need to make two changes to the previous example. Firstly, we must reuse the same Exchange object on the application side:

Exchange exchange = template.getCamelContext()
  .getEndpoint("direct:startChatWithMemory")
  .createExchange();

for (String prompt : prompts) {
    exchange.getMessage()
      .setBody(prompt);
    template.send("direct:startChatWithMemory", exchange);
                
aiResponses.add(exchange.getMessage()
  .getBody(String.class));

Once the Exchange object is created, namely exchange in the code snippet above, we use the same object in a for loop for processing the prompts. The prompts variable is a list of strings containing the prompts:

List<String> prompts = List.of("My name is Burak and I am a Java developer.", "What is my name and what do I do?"
);

The first prompt specifies the user’s name and job, and the second prompt asks them.

Secondly, we must set the conversationMemory option to true on the router side:

.to("openai:chat-completion?conversationMemory=true")

When we run the application, the response to the first prompt, “My name is Burak and I am a Java developer.”, is as follows:

Hi Burak! How can I assist you today with your Java development?

Let’s see the response to the second prompt, “What is my name and what do I do?”:

Your name is Burak, and you are involved in Java development. How can I assist you further?

Therefore, reusing the same Exchange object while setting the conversationMemory option to true enables us to persist storage between calls.

6. Providing Structured Output

The OpenAI component can also present the LLM’s response in a deterministic format. For example, we can map the response to a POJO (Plain Old Java Object), which can be directed safely to a database. Let’s discuss an example that returns a book’s name, author, and summary:

public static BookInfo runChat(String userPrompt) throws Exception {
    ...
    BookInfo bookInfo = template.requestBody("direct:startBookChat", userPrompt, BookInfo.class);
    return bookInfo;
    ...
}

The runChat() member function in the above code snippet takes a user prompt as a string and passes it to the router by calling the requestBody() method of ProducerTemplate. The third argument of requestBody(), BookInfo.class, specifies the response to be a BookInfo object. The BookInfo class consists of three public member variables:

public class BookInfo {

    public String name;
    public String author;
    public String summary;
}

We also need to update the router class:

from("direct:startBookChat")
  .setHeader(OpenAIConstants.SYSTEM_MESSAGE, constant("You are a helpful, brief book advisor."))
  .setHeader(OpenAIConstants.MODEL, constant("gpt-4o-mini"))
  .setHeader(OpenAIConstants.TEMPERATURE, constant(0.1))
  .to("openai:chat-completion?outputClass=com.baeldung.apachecamel.BookInfo")
  .unmarshal()
  .json(JsonLibrary.Jackson, BookInfo.class)
  .log("AI Response: ${body}");

Here, we use the outputClass option to instruct the LLM to respond in JSON format that matches the BookInfo schema. The next calls, unmarhal() and json(JsonLibrary.Jackson, BookInfo.class), use the Apache Camel JSON Jackson component. They convert the raw JSON string in the message body and deserialize it to BookInfo.

Let’s see an example of asking for the author and summary of a book, The Time Regulation Institute:

String userPrompt = "Can you provide the author and summary of the following book: The Time Regulation Institute";
BookInfo book = runChat(userPrompt);
LOGGER.info("Book: {}", book.name);
LOGGER.info("Author: {}", book.author);
LOGGER.info("Summary: {}", book.summary);

Here is the abbreviated output:

Book: The Time Regulation Institute
Author: Ahmet Hamdi Tanpınar
Summary: A satirical novel that explores the absurdities of modernity and the clash between 
tradition and progress in Turkish society, focusing on the establishment of a time regulation 
institute and its impact on the characters' lives.

The output shows that the response was successfully mapped to the Java class BookInfo.

7. Conclusion

In this article, we discussed LLM integration with the Apache Camel OpenAI component.

Firstly, we learned how to send prompts to an LLM using Camel routes and implemented a chat completion application based on them. Then, we saw how to change the OpenAI-compatible endpoint. We also learned to maintain conversational memory so that the model can remember previous prompts throughout a chat. Finally, we discussed formatting an LLM’s responses in a predefined format.

Besides the applications we discussed, there are other use cases. For example, we can process an LLM’s responses chunk by chunk rather than waiting for the full completion of a response. We have to set the streaming option to true. Another example is the support for multi-modal inputs. We can use text files and images with vision-capable models.

As usual, the complete source code for the examples is available over on GitHub.

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

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)
guest
0 Comments
Oldest
Newest
Inline Feedbacks
View all comments