PII Redaction Protocol: Build a Spy-Themed Privacy Dashboard with Quarkus, Presidio, and LangChain4j
Learn how to build a spy-themed web app with Quarkus, Presidio, and LangChain4j that detects and redacts PII from AI-generated content.
Every organization has its secrets. But when those secrets involve personally identifiable information: Names, phone numbers, email addresses, then it's not just spycraft. It's compliance. GDPR. CCPA. Legal landmines.
In this tutorial, you’ll build a privacy-respecting, AI-powered mission control dashboard that detects and redacts sensitive data using open source tooling. Our tech stack includes Quarkus, Microsoft Presidio, LangChain4j, and Ollama. And because security doesn’t have to be boring, we’ll wrap it all in a retro spy theme for good measure.
Your mission, should you choose to accept it, begins now.
The Tech Briefing
Before you’re dropped behind enemy lines, let’s review your tools:
Quarkus: Your fast-starting Java framework for building REST APIs and full-stack web apps.
LangChain4j + Ollama: Generates dynamic, AI-written mission briefings using a local LLM.
Quarkus Presidio: Automatically detects and redacts PII like names, emails, and phone numbers.
HTML/CSS/JS Frontend: A spy-themed UI for managing mission briefings.
Let’s boot up the command center.
Phase 1: Setting Up Your Field Kit
You’ll need the following before heading out:
Java 17+
Maven 3.8.1+
Podman (Presidio runs as a Dev Service in containers)
Ollama (Optional: Install from ollama.com and run
ollama pull llama3
)
With your gear in place, let’s build the project.
Phase 2: Bootstrapping the Mission Control App
Use Maven to generate your Quarkus project with all the right gadgets:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=org.mi6 \
-DprojectArtifactId=mission-control \
-Dextensions="rest-jackson,quarkus-presidio,quarkus-langchain4j-ollama"
cd mission-control
Phase 3: Connecting to Your AI Handler
LangChain4j needs to know which local LLM to use. Open the project in your IDE and head to src/main/resources/application.properties
.
# Ollama LLM configuration
quarkus.langchain4j.ollama.chat-model.model-name=llama3
quarkus.langchain4j.ollama.timeout=120s
Quarkus will either spin it up in a Dev Container or use the local Ollama instance. So you either need Podman/Docker or local Ollama running. This gives us a local, fast, and privacy-safe LLM.
Phase 4: The AI Mission Generator
We’ll use LangChain4j to create mission briefings. Define an interface like so:
package org.mi6;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
@RegisterAiService
public interface MissionGenerator {
@SystemMessage("""
You are a mission creator for a top-secret, yet slightly absurd, spy agency.
Generate a short, funny mission briefing. The mission must include:
- A fictional contact person's full name.
- A secret code name for the agent (the user).
- A specific location (address, city).
- A date and time for a rendezvous.
- An email address for contact.
- A phone number for emergency contact.
""")
@UserMessage("Generate a new, unique mission briefing for agent {agentName}.")
String generateMission(String agentName);
}
Quarkus will generate the implementation at runtime and you can inject it like any other CDI bean.
Phase 5: Backend Intelligence Operations
Let’s wire the first pieces together: detect PII, highlight it for display, and redact it when accepted.
package org.mi6;
import io.quarkiverse.presidio.runtime.Analyzer;
import io.quarkiverse.presidio.runtime.Anonymizer;
import io.quarkiverse.presidio.runtime.model.AnalyzeRequest;
import io.quarkiverse.presidio.runtime.model.AnonymizeRequest;
import io.quarkiverse.presidio.runtime.model.AnonymizeResponse;
import io.quarkiverse.presidio.runtime.model.Mask;
import io.quarkiverse.presidio.runtime.model.RecognizerResultWithAnaysisExplanation;
import io.quarkiverse.presidio.runtime.model.Replace;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.Collections;
import java.util.List;
@ApplicationScoped
public class MissionService {
@Inject
MissionGenerator model;
@Inject
@RestClient
Analyzer analyzer;
@Inject
@RestClient
Anonymizer anonymizer;
// Define specific anonymization strategies for our mission briefings
private static final Replace PERSON_REPLACE = new Replace("[AGENT_NAME]");
private static final Mask PHONE_MASK = new Mask("*", 4, true); // Mask all but last 4 digits
private static final Replace EMAIL_REPLACE = new Replace("[REDACTED_EMAIL]");
private static final Replace LOCATION_REPLACE = new Replace("[CLASSIFIED_LOCATION]");
private static final Replace DATE_REPLACE = new Replace("[CLASSIFIED_DATE]");
private static final Replace DEFAULT_REPLACE = new Replace("*****");
/**
* A record to hold all the results of our operation.
* This will be serialized to JSON and sent to the UI.
*/
public record Mission(
String originalText,
String redactedText,
List<RecognizerResultWithAnaysisExplanation> entities
) {}
/**
* Generates a new mission, analyzes it for PII, and prepares a redacted version.
*
* @return A Mission object containing original text, redacted text, and found entities.
*/
public Mission generateAndAnalyzeMission() {
// Step 0: Generate a mission from the Ollama model
String missionText = model.generateMission("Ethan Hunt");
// Step 1: Analyze the text to find all PII entities
var analyzeRequest = new AnalyzeRequest();
analyzeRequest.text(missionText);
analyzeRequest.language("en");
List<RecognizerResultWithAnaysisExplanation> recognizerResults = analyzer.analyzePost(analyzeRequest);
// Step 2: Define the anonymization request with our custom strategies
var anonymizeRequest = new AnonymizeRequest();
anonymizeRequest.setText(missionText);
anonymizeRequest.analyzerResults(Collections.unmodifiableList(recognizerResults));
// Apply our specific strategies
anonymizeRequest.putAnonymizersItem("DEFAULT", DEFAULT_REPLACE);
anonymizeRequest.putAnonymizersItem("PERSON", PERSON_REPLACE);
anonymizeRequest.putAnonymizersItem("LOCATION", LOCATION_REPLACE);
anonymizeRequest.putAnonymizersItem("PHONE_NUMBER", PHONE_MASK);
anonymizeRequest.putAnonymizersItem("EMAIL_ADDRESS", EMAIL_REPLACE);
anonymizeRequest.putAnonymizersItem("DATE_TIME", DATE_REPLACE);
// Step 3: Call the anonymizer to get the redacted text
AnonymizeResponse anonymizeResponse = anonymizer.anonymizePost(anonymizeRequest);
String anonymizedText = anonymizeResponse.getText();
// Step 4: Package everything into our Mission record and return it
return new Mission(missionText, anonymizedText, recognizerResults);
}
}
Phase 6: Generate the mission
Now we just need a REST endpoint for the missions:
package org.mi6;
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("/mission")
public class MissionResource {
@Inject
MissionService missionService;
@GET
@Path("/new")
@Produces(MediaType.APPLICATION_JSON)
public MissionService.Mission getNewMission() {
return missionService.generateAndAnalyzeMission();
}
}
Phase 7: Building the Frontend Dashboard
Create a src/main/resources/META-INF/resources
directory and add the following files. You can find them in my Github Repository.
index.html
: Spy-themed interfacestyle.css
: Retro visuals with hoverable PII tagsapp.js
: Handles fetching and accepting missions
Each mission briefing shows the full text, with yellow highlights for detected PII. Accepting the mission triggers redaction and replaces sensitive info with <REDACTED>
placeholders.
Phase 7: Run the Mission
Fire up the command center:
./mvnw quarkus:dev
If Podman and Ollama are active, Quarkus will:
Start Presidio via Dev Services
Connect to your local LLM
Expose the
/mission
endpointServe your frontend at
http://localhost:8080
Click "Request New Mission" and watch as LangChain4j and Presidio work together to generate and scrub a spy briefing in real time.
Final Debrief
With this project, you’ve built more than a toy. You've created a working demonstration of responsible AI and privacy-first development. You now know how to:
Use Quarkus LangChain4j with a local Ollama model
Detect and classify PII using Presidio
Redact sensitive data automatically
Build an interactive UI to tie it all together
In a world where privacy violations make headlines and compliance fines soar, you've built a digital safehouse.
Nice work, agent. Mission accomplished.