Captain’s Log, Stardate Java: Building a Quarkus-Powered AI Sci-Fi App with Langchain4j and Ollama
Use the power of local LLMs, Quarkus magic, and Langchain4j tool calling to generate dynamic, weekday-aware space captain logs without cloud APIs.
“Captain’s log, stardate 58536.99. Morale is dropping faster than our caffeine supply. The crew blames Jenkins.”
In this tutorial, we’re going to build something absurdly useful: a web app that generates AI-powered, sci-fi-inspired captain’s logs—complete with dynamic weekday-aware commentary. Whether it's Monday’s dread or Friday’s false hope, our AI captain has thoughts. And you’ll learn how to make it happen using Quarkus, Langchain4j, and a local LLM served by Ollama.
Let’s launch the ship.
Create the Quarkus Project
Let’s start by generating a Quarkus project with all required extensions:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=captains-log-generator \
-Dextensions="rest-jackson,langchain4j-ollama"
cd captains-log-generator
This includes:
quarkus-rest-jackson
: to build the REST endpointquarkus-langchain4j-ollama
: to connect with a local Ollama-managed LLM
And as usual, if you like, you can jump directly to the source in my Github repository.
Set Up Ollama and configure Quarkus
Langchain4j needs a running LLM. You can use the Quarkus Dev Service or have it detect a local, running Ollama instance.
Open src/main/resources/application.properties
and configure the Ollama model to use:
quarkus.langchain4j.ollama.chat-model.model-id=llama3.1:8b
quarkus.langchain4j.ollama.log-requests=true
quarkus.langchain4j.ollama.log-responses=true
quarkus.langchain4j.ollama.temperature=0.0
quarkus.langchain4j.ollama.timeout=30s
Define the Define the AI Captain Service
Create the interface that defines how Quarkus will talk to your LLM.
Create src/main/java/org/acme/CaptainsLogService.java
:
package org.acme;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
@RegisterAiService(tools = { DateTool.class, StardateTool.class })
public interface CaptainsLogService {
@SystemMessage("""
You are the AI of a starship. You generate daily captain's log entries with dramatic flair, light sarcasm, and references to life aboard the ship.
MANDATORY: You MUST call BOTH tools:
1. FIRST call getTodaysStardate() to get the exact stardate
2. THEN call getWeekdayMood() to get the weekday context
NEVER generate stardates yourself - always use the getTodaysStardate() tool.
Keep logs short and humorous.
""")
@UserMessage("Captain, generate today's log entry. Remember to use both tools!")
String generateLog();
}
This interface defines a method that will be implemented behind the scenes using an LLM via Langchain4j. The @SystemMessage
sets the context, and @UserMessage
defines what we’re asking the AI to do and the (tools =…)
are defined on the @RegisterAiService
.
Create the Weekday Commentary and Stardate Tool
This tool returns dynamic commentary based on the current day. It’s what gives our captain “context” for each log entry. It also calculates the Stardate. At least in a version that I think makes sense. Learn more about Stardates if you like.
Create src/main/java/org/acme/DateTool.java
:
package org.acme;
import java.time.DayOfWeek;
import java.time.LocalDate;
import dev.langchain4j.agent.tool.Tool;
import io.quarkus.logging.Log;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class DateTool {
@Tool("Returns the current weekday mood.")
public String getWeekdayMood() {
DayOfWeek day = LocalDate.now().getDayOfWeek();
Log.infof("Day of week: %s", day);
return switch (day) {
case MONDAY -> "Monday. Morale is low. Systems reboot slowly. Coffee reserves critically low.";
case TUESDAY -> "Tuesday. The crew has accepted their fate. Productivity stabilizing.";
case WEDNESDAY -> "Wednesday. Captain calls it 'The Void'. Navigational charts show signs of hope.";
case THURSDAY -> "Thursday. Spirits rise. Jenkins wears socks with spaceships.";
case FRIDAY -> "Friday. Celebration imminent. Warp cores warming up for weekend retreat.";
case SATURDAY -> "Saturday. The ship is quiet. Recreation mode enabled.";
case SUNDAY -> "Sunday. Maintenance day. AI systems suggest grill routines.";
};
}
}
Create src/main/java/org/acme/StardateTool.java
:
package org.acme;
import java.time.LocalDate;
import java.time.Month;
import dev.langchain4j.agent.tool.Tool;
import io.quarkus.logging.Log;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class StardateTool {
private static final double BASE_CONSTANT = 58000.00;
private static final int BASE_YEAR = 2025;
@Tool("Returns Stardate based on today's date.")
public String getTodaysStardate() {
LocalDate today = LocalDate.now();
int year = today.getYear();
int day = today.getDayOfMonth();
boolean isLeap = today.isLeapYear();
int daysInYear = isLeap ? 366 : 365;
int m = getAdjustedMonthNumber(today.getMonth(), isLeap);
double stardate = BASE_CONSTANT
+ (1000.0 * (year - BASE_YEAR))
+ ((1000.0 / daysInYear) * (m + day - 1));
Log.infof("Stardate: %s", stardate);
return String.format("Stardate %.2f", stardate);
}
public int getAdjustedMonthNumber(Month month, boolean isLeap) {
return switch (month) {
case JANUARY -> 0;
case FEBRUARY -> 31;
case MARCH -> isLeap ? 60 : 59;
case APRIL -> isLeap ? 91 : 90;
case MAY -> isLeap ? 121 : 120;
case JUNE -> isLeap ? 152 : 151;
case JULY -> isLeap ? 182 : 181;
case AUGUST -> isLeap ? 213 : 212;
case SEPTEMBER -> isLeap ? 244 : 243;
case OCTOBER -> isLeap ? 274 : 273;
case NOVEMBER -> isLeap ? 305 : 304;
case DECEMBER -> isLeap ? 335 : 334;
};
}
}
Langchain4j will automatically invoke these method when the model sees it as relevant to the task.
Build the REST Resource
Expose the AI service through a REST endpoint that renders an HTML page. Rename the GreetingResource.java and add below to it src/main/java/org/acme/CaptainsLogResource.java
:
package org.acme;
import io.quarkus.qute.Template;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/")
public class CaptainsLogResource {
@Inject
CaptainsLogService logService;
@Inject
Template log;
@GET
@Produces(MediaType.TEXT_HTML)
public String get() {
return log.data("entry", logService.generateLog()).render();
}
}
This injects the AI service and passes the generated joke into a Qute template.
Create the Qute Template
Now create the HTML template. Save this to src/main/resources/templates/log.html
:
<!DOCTYPE html>
<html>
<head>
<title>Captain's Log Generator</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<h1>Captain's Log</h1>
<p class="entry">{entry}</p>
<a href="/" class="button">Log Another Day</a>
</div>
</body>
</html>
Note: The
<link rel="stylesheet" href="/styles.css">
assumes you’ll serve a static CSS file at/src/main/resources/META-INF/resources/styles.css
. You can define your styles there instead of embedding them in the HTML. Grab a ready made proposal from my Github repository.
Engage!
Start your application in dev mode:
./mvnw quarkus:dev
Then visit http://localhost:8080
You’ll be greeted by your dad joke app, now infused with weekday wisdom.
Captain's Log, Stardate 58536.99:
Today was a bit of a slog. Nothing exciting happened, but that's what happens on Wednesdays - or as I like to call it, "The Void". Our navigational charts showed some signs of hope, though. Maybe tomorrow will be better.
In other news, Lieutenant Commander S'takk is still trying to get the ship's espresso machine working. I'm starting to think that's a lost cause.
Signing off,
Captain Zara
Click "Log Another Day" for a new entry.
That’s a Wrap
In this tutorial, you learned how to:
A Quarkus app using Langchain4j and a local LLM
A declarative AI service with tool calling
An HTML frontend with dynamic content
A sci-fi writing machine with a personality
This project is funny, creative, and surprisingly powerful. You now have the tools to build AI-driven content systems entirely in Java with Quarkus and all without relying on cloud APIs or Python wrappers.
Now go forth and automate your space logs. The galaxy depends on it.