It Has Never Been This Easy to Build Gen AI Features in Java

Published: (February 10, 2026 at 05:24 PM EST)
10 min read
Source: Dev.to

Source: Dev.to

Introduction

Building generative AI applications in Java used to be a complex, boiler‑plate‑heavy endeavor. You’d wrestle with raw HTTP clients, hand‑craft JSON payloads, parse streaming responses, manage API keys, and stitch together observability—all before writing a single line of actual AI logic. Those days are over.

Genkit Java is an open‑source framework that makes building AI‑powered applications in Java as straightforward as defining a function. Pair it with Google’s Gemini models and Google Cloud Run, and you can go from zero to a production‑deployed generative AI service in minutes, not days.

This is a complete, working example. Clone it, set your API key, and run.


Who is this for?

If you’re a Java developer, you’ve probably watched the Gen AI revolution unfold mostly in Python and TypeScript. The tooling, the frameworks, the tutorials—all skewed toward those ecosystems. Java developers were left to either build everything from scratch or use verbose, low‑level SDKs.


What the example shows

A Java application with a translation AI flow powered by Gemini via Genkit, showcasing:

  • Typed flow inputsTranslateRequest class with @JsonProperty annotations.
  • Structured LLM output – Gemini returns a TranslateResponse Java object directly (no manual JSON parsing).
  • Typed flow outputs – The flow returns a fully typed TranslateResponse to the caller.

All of this lives in a single Java file plus two model classes. No Spring Boot, no annotation soup, no XML configuration—just clean, readable, type‑safe code.


Prerequisites

ToolMinimum version
Java21+ (Eclipse Temurin recommended)
Maven3.6+
Node.js18+ (for the Genkit CLI)
Google GenAI API keyFree from Google AI Studio
Google Cloud SDKOnly for Cloud Run deployment

Install the Genkit CLI

The Genkit CLI is your command‑line companion for developing and testing AI flows.

npm install -g genkit

Verify the installation:

genkit --version

The CLI powers the Dev UI and provides a seamless development experience (more on that below).


Project structure

genkit-java-getting-started/
├── src/
│   └── main/
│       ├── java/
│       │   └── com/example/
│       │       ├── App.java                # ← Main application
│       │       ├── TranslateRequest.java   # ← Typed flow input
│       │       └── TranslateResponse.java  # ← Typed flow + LLM output
│       └── resources/
│           └── logback.xml                 # Logging configuration
├── pom.xml                                 # Maven config with Genkit + Jib
├── run.sh                                  # Quick‑start script
└── README.md                               # This article

Quick‑start

git clone https://github.com/xavidop/genkit-java-getting-started.git
cd genkit-java-getting-started

export GOOGLE_API_KEY=your-api-key-here

genkit start -- mvn compile exec:java

That’s it—two commands. Your AI‑powered Java server runs at http://localhost:8080, and the Genkit Dev UI is available at http://localhost:4000.

You can also run the app directly:

mvn compile exec:java

Model classes

TranslateRequest.java – flow input

package com.example;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

/**
 * Input for the translate flow.
 */
public class TranslateRequest {

    @JsonProperty(required = true)
    @JsonPropertyDescription("The text to translate")
    private String text;

    @JsonProperty(required = true)
    @JsonPropertyDescription("The target language (e.g., Spanish, French, Japanese)")
    private String language;

    public TranslateRequest() {}

    public TranslateRequest(String text, String language) {
        this.text = text;
        this.language = language;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    @Override
    public String toString() {
        return String.format("TranslateRequest{text='%s', language='%s'}", text, language);
    }
}

TranslateResponse.java – flow output & LLM structured output

package com.example;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

/**
 * Structured output for the translate flow.
 */
public class TranslateResponse {

    @JsonProperty(required = true)
    @JsonPropertyDescription("The original text that was translated")
    private String originalText;

    @JsonProperty(required = true)
    @JsonPropertyDescription("The translated text")
    private String translatedText;

    @JsonProperty(required = true)
    @JsonPropertyDescription("The target language")
    private String language;

    public TranslateResponse() {}

    public TranslateResponse(String originalText, String translatedText, String language) {
        this.originalText = originalText;
        this.translatedText = translatedText;
        this.language = language;
    }

    public String getOriginalText() {
        return originalText;
    }

    public void setOriginalText(String originalText) {
        this.originalText = originalText;
    }

    public String getTranslatedText() {
        return translatedText;
    }

    public void setTranslatedText(String translatedText) {
        this.translatedText = translatedText;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    @Override
    public String toString() {
        return String.format(
            "TranslateResponse{originalText='%s', translatedText='%s', language='%s'}",
            originalText, translatedText, language);
    }
}

Note: The @JsonPropertyDescription annotations are key. Genkit passes them to Gemini as part of the JSON schema, so the model knows exactly what each field represents.


Now you have a clean, ready‑to‑run Java project that demonstrates typed inputs, structured LLM outputs, and a minimal‑boilerplate development experience with Genkit. Happy coding!

Genkit Java Setup

Genkit genkit = Genkit.builder()
    .options(GenkitOptions.builder()
        .devMode(true)
        .reflectionPort(3100)
        .build())
    .plugin(GoogleGenAIPlugin.create())
    .plugin(jetty)
    .build();

The GoogleGenAIPlugin reads your GOOGLE_API_KEY automatically.
The JettyPlugin handles HTTP.
Genkit wires everything together.


Defining the translate flow

genkit.defineFlow(
    "translate",
    TranslateRequest.class,      // ← typed input
    TranslateResponse.class,     // ← typed output
    (ctx, request) -> {
        String prompt = String.format(
            "Translate the following text to %s.\n\nText: %s",
            request.getLanguage(),
            request.getText()
        );

        return genkit.generate(
            GenerateOptions.builder()
                .model("googleai/gemini-3-flash-preview")
                .prompt(prompt)
                .outputClass(TranslateResponse.class)   // ← Gemini returns a typed object!
                .config(GenerationConfig.builder()
                    .temperature(0.1)
                    .build())
                .build()
        );
    }
);

What’s happening?

StepExplanation
TranslateRequest.class as the flow inputGenkit automatically deserializes incoming JSON into a TranslateRequest object – no manual Map.get() casting.
TranslateResponse.class as the flow outputThe flow returns a typed object that Genkit serializes to JSON for the HTTP response.
outputClass(TranslateResponse.class) on the generate callGenkit sends the JSON schema derived from TranslateResponse to Gemini. Gemini returns structured JSON, which Genkit deserializes back into a TranslateResponse object – no manual parsing needed.
Single defineFlow call• Registers the flow in Genkit’s internal registry
• Exposes it as a POST /api/flows/translate endpoint
• Makes it visible in the Dev UI
• Adds full OpenTelemetry tracing automatically
• Tracks token usage, latency, and error rates

Compare this to writing a Spring Boot controller + service + DTO + config + exception handler for the same functionality. Genkit eliminates all that boilerplate.


Development Experience

Running genkit start launches a visual Dev UI at :

  • Browse all flows – see translate with its typed input/output schemas.
  • Run flows interactively – fill in a TranslateRequest JSON, click Run, and see the TranslateResponse instantly (no curl needed).
  • Inspect traces – view which model was called, the input/output, execution time, and token usage.
  • View registered models & tools – see all available Gemini models and any custom tools you’ve defined.
  • Test tool calling – watch Gemini decide to call your tools in real‑time.
  • Manage datasets & evaluations – create test datasets and evaluate AI outputs.

Building & Deploying with Jib

The project uses Jib to build and push container images directly from Maven—no Dockerfile, no Docker daemon.

# Set your GCP project
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1

# Build the container image and push it to Google Container Registry
# (No Docker needed – Jib does it all from Maven!)
mvn compile jib:build -Djib.to.image=gcr.io/$PROJECT_ID/genkit-java-app

# Deploy to Cloud Run
gcloud run deploy genkit-java-app \
  --image gcr.io/$PROJECT_ID/genkit-java-app \
  --region $REGION \
  --platform managed \
  --allow-unauthenticated \
  --set-env-vars "GOOGLE_API_KEY=$GOOGLE_API_KEY" \
  --memory 512Mi \
  --cpu 1

Why Jib?

  • No Dockerfile – container image is built directly from the Maven project.
  • No Docker daemon – Docker isn’t required on your machine.
  • Fast rebuilds – dependencies, classes, and resources are layered; only changed layers are rebuilt.
  • Reproducible builds – deterministic and independent of the local Docker environment.
  • Direct push – image is sent straight to GCR/Artifact Registry without a local docker push.

Optional: Build a local Docker image (requires Docker)

mvn compile jib:dockerBuild -Djib.to.image=genkit-java-app

Testing the Translate Flow

Once the server is running, send a TranslateRequest JSON and receive a structured TranslateResponse.

curl -X POST http://localhost:8080/api/flows/translate \
  -H 'Content-Type: application/json' \
  -d '{"text": "Building AI applications has never been easier", "language": "Spanish"}'

Example response

{
  "originalText": "Building AI applications has never been easier",
  "translatedText": "Construir aplicaciones de IA nunca ha sido tan fácil",
  "language": "Spanish"
}

Try other languages

# French
curl -X POST http://localhost:8080/api/flows/translate \
  -H 'Content-Type: application/json' \
  -d '{"text": "Genkit makes Java AI development simple", "language": "French"}'

# Japanese
curl -X POST http://localhost:8080/api/flows/translate \
  -H 'Content-Type: application/json' \
  -d '{"text": "Hello world", "language": "Japanese"}'

Notice the response is always a structured JSON object, not a raw string. That’s the power of outputClass(TranslateResponse.class): Gemini returns structured data that Genkit deserializes into your Java class automatically.


Production‑Grade Features

When you use Genkit you get more than a thin wrapper around API calls:

  • OpenTelemetry tracing for every flow execution

    • Latency tracking per flow and per model call
    • Token usage (input / output / “thinking” tokens)
    • Error rates and failure tracking
    • Full span hierarchy showing the execution path
  • Model swapping – change the underlying model with a single line:

// Switch from Gemini to OpenAI
.plugin(OpenAIPlugin.create())

// Or use Anthropic Claude
.plugin(AnthropicPlugin.create())

Genkit gives you a production‑ready framework for building, testing, and deploying AI‑powered Java applications with minimal boilerplate and maximum observability.

Getting Started with Genkit Java

// Or run locally with Ollama
.plugin(OllamaPlugin.create())

Genkit supports 10+ model providers, vector databases (Pinecone, Weaviate, PostgreSQL), Firebase integration, and more. This is where Genkit really shines for Java developers: flows, generate calls, and even LLM responses are fully typed.

Defining a Typed Flow

// The flow takes a TranslateRequest and returns a TranslateResponse
genkit.defineFlow("translate", TranslateRequest.class, TranslateResponse.class, ...);

Generating Typed Responses

// The LLM returns a TranslateResponse directly, no string parsing
genkit.generate(
    GenerateOptions.builder()
        .outputClass(TranslateResponse.class)
        .build()
);

Genkit derives JSON schemas from your @JsonProperty and @JsonPropertyDescription annotations and sends them to Gemini, so the model returns structured data that maps directly to your Java classes. No object casting, no response.getText() + objectMapper.readValue(), no runtime surprises.


What This Project Covers

  • RAG – Retrieval‑Augmented Generation with vector stores (Firestore, Pinecone, pgvector, Weaviate)
  • Multi‑agent orchestration – Coordinate multiple AI agents
  • Chat sessions – Multi‑turn conversations with session persistence
  • Evaluations – RAGAS‑style metrics to measure your AI output quality
  • MCP Integration – Connect to Model Context Protocol servers
  • Spring Boot – Use the Spring plugin instead of Jetty for existing Spring apps
  • Firebase – Deploy as Cloud Functions with Firestore vector search

Explore the full Genkit Java documentation and the samples directory to dive deeper.


Why Use Genkit Java?

  • Typed inputs/outputs eliminate parsing errors.
  • Structured LLM responses map directly to Java classes.
  • Built‑in observability for easy debugging.
  • Seamless deployment to cloud platforms.

Result: Building powerful generative AI applications with minimal code.

You can find the full code of this example in the GitHub repository.

Happy coding!

0 views
Back to Blog

Related posts

Read more »

New article

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as we...

Build a Serverless RAG Engine for $0

Introduction: The Problem with “Toy” RAG Apps Most RAG tutorials skip the hard parts that actually matter in production: - No security model: Users can access...

Set up Ollama, NGROK, and LangChain

markdown !Breno A. V.https://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fu...