Quarkus Getting Started

This guide walks you through integrating Memory Service with a Quarkus AI agent. You’ll start with basic chat memory and can progressively add features in the follow-up guides.

Prerequisites

  • Java 21 or later
  • Maven (or use the included ./mvnw wrapper)
  • Docker (for Dev Services)
  • curl and jq installed

Build from Source

Note: This project is currently in the proof-of-concept (POC) phase and has not yet published any releases. You’ll need to build it from source before using it in your project.

Clone the repository and build all modules:

git clone https://github.com/chirino/memory-service.git
cd memory-service
./mvnw -DskipTests clean install

This installs all project artifacts to your local Maven repository.

Step 1: Create a Simple LangChain4j App

First, let’s create a new Quarkus application with:

quarkus create example
cd example

Add the LangChain4j OpenAI dependency to your pom.xml:

<dependency>
  <groupId>io.quarkiverse.langchain4j</groupId>
  <artifactId>quarkus-langchain4j-openai</artifactId>
  <version>1.5.0.CR2</version>
</dependency>

Create a simple AI service interface:

package org.acme;

import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
@RegisterAiService
public interface Agent {
    String chat(String userMessage);
}

Create a REST resource to expose the agent:

package org.acme;

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("/chat")
public class ChatResource {

    @Inject
    Agent agent;

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

Configure your LLM in application.properties so it gets OpenAI credentials from the environment variables:

quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY}
quarkus.langchain4j.openai.base-url=${OPENAI_BASE_URL:https://api.openai.com}/v1

Change the HTTP port to 9090 to avoid conflict with services that will be started later by updating application.properties:

quarkus.http.port=9090

Run your agent:

export OPENAI_API_KEY=your-api-key
./mvnw quarkus:dev

Test it with curl:

curl -NsSfX POST http://localhost:9090/chat \
  -H "Content-Type: text/plain" \
  -d "Hi, I'm Hiram, who are you?"

curl -NsSfX POST http://localhost:9090/chat \
  -H "Content-Type: text/plain" \
  -d "Who am I?"

This works, but you may have noticed that the agent has no memory. Each request is independent. Let’s add conversation memory.

Step 2: Add Memory Service Extension

Add the Memory Service extension to your pom.xml:

<dependency>
  <groupId>io.github.chirino.memory-service</groupId>
  <artifactId>memory-service-extension</artifactId>
  <version>999-SNAPSHOT</version>
</dependency>

Update the Agent interface to accept a conversation ID using the @MemoryId annotation:

package org.acme;

import dev.langchain4j.service.MemoryId;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
@RegisterAiService
public interface Agent {
    String chat(@MemoryId String conversationId, String userMessage);
}

Update the REST resource to accept a conversation ID:

package org.acme;

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

@Path("/chat")
public class ChatResource {

    @Inject
    Agent agent;

    @POST
    @Path("/{conversationId}")
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.TEXT_PLAIN)
    public String chat(
            @PathParam("conversationId") String conversationId,
            String userMessage) {
        return agent.chat(conversationId, userMessage);
    }
}

Start the Memory Service in Docker Compose following the Getting Started guide.

Configure the agent to connect to the Memory Service by setting the memory-service.client.url and memory-service.client.api-key in application.properties:

memory-service.client.url=http://localhost:8082
memory-service.client.api-key=agent-api-key-1

The ChatMemory provider for the Memory Service extension requires that the user request is authenticated. Configure the OIDC configuration in application.properties:

quarkus.oidc.auth-server-url=http://localhost:8081/realms/memory-service
quarkus.oidc.token-issuer=http://localhost:8081/realms/memory-service
quarkus.oidc.client-id=memory-service-client
quarkus.oidc.credentials.secret=change-me

quarkus.http.auth.permission.authenticated.paths=/chat/*
quarkus.http.auth.permission.authenticated.policy=authenticated

Run your agent again:

export OPENAI_API_KEY=your-api-key
./mvnw quarkus:dev

Make sure you define a shell function that can get the bearer token for the bob user:

function get-token() {
  curl -sSfX POST http://localhost:8081/realms/memory-service/protocol/openid-connect/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "client_id=memory-service-client" \
    -d "client_secret=change-me" \
    -d "grant_type=password" \
    -d "username=bob" \
    -d "password=bob" \
    | jq -r '.access_token'
}  

Test it with curl—now with conversation memory:

curl -NsSfX POST http://localhost:9090/chat/3579aac5-c86e-4b67-bbea-6ec1a3644942 \
  -H "Content-Type: text/plain" \
  -H "Authorization: Bearer $(get-token)" \
  -d "Hi, I'm Hiram, who are you?"

curl -NsSfX POST http://localhost:9090/chat/3579aac5-c86e-4b67-bbea-6ec1a3644942 \
  -H "Content-Type: text/plain" \
  -H "Authorization: Bearer $(get-token)" \
  -d "Who am I?"

The agent now remembers context within the same conversation ID!

If you browse to the demo agent app at http://localhost:8080/, you will see that a conversation has been created with the ID 3579aac5-c86e-4b67-bbea-6ec1a3644942. But it won’t show any messages. That’s because we are not yet storing what we call the history of the conversation. The only thing being stored is the agent memory, and that’s not typically what you want to display to a user in a UI.

Next Steps

Continue to Conversation History to learn how to:

  • Record conversation history for frontend display
  • Expose conversation APIs (messages, listing)
  • Build a complete chat UI experience

Or jump ahead to Advanced Features for:

  • Conversation forking
  • Streaming responses
  • Response resumption and cancellation

Complete Example

For a complete working example, see the examples/chat-quarkus directory in the repository.