The AI That Whispers Errors: Securing Human-Friendly Feedback with Quarkus and LangChain4j
Learn how to build smarter, safer Java applications by transforming technical errors into human-friendly messages with built-in PII guardrails.
You’ve been there. An internal error. A stack trace so deep it could be a Lovecraftian horror. The user sees 500 Internal Server Error
, and you see a developer ticket that starts with: "What does this mean? Help."
But what if the error could explain itself—like an apologetic but helpful ghost? Even better: what if that explanation was AI-generated, readable by humans, and scrubbed clean of any sensitive data before it left the safety of your system?
This isn't a pipe dream. It’s Quarkus, LangChain4j, and a little PII protection magic.
Welcome to your next level of exception handling.
The Setup – A Quarkus Project with Brains and Boundaries
Let’s build a Quarkus application that:
Throws a custom exception (with potential PII).
Handles it via a JAX-RS
ExceptionMapper
.Redacts sensitive content.
Invokes an AI model to generate a human-readable message.
Returns a secure JSON response to the client.
And it does all of this with LangChain4j, using a content filter guardrail to intercept and clean up user messages before sending them to the LLM. If you can’t find time to build it yourself, you can look at the code in my Github repository.
Project Kickstart
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=ai-error-handler-pii \
-Dextensions="rest-jackson,langchain4j-ollama"
cd ai-error-handler-pii
Now edit src/main/resources/application.properties
to wire in your Ollama model (Mistral in this case).
quarkus.langchain4j.ollama.chat-model.model-id=mistral
quarkus.langchain4j.ollama.timeout=60s
quarkus.langchain4j.ollama.log-requests=true
quarkus.langchain4j.ollama.log-responses=true
The Error Awakens
Create a throwable custom exception:
// MyCustomApplicationException.java
public class MyCustomApplicationException extends RuntimeException {
public MyCustomApplicationException(String message) {
super(message);
}
}
Simulate a failing endpoint that leaks just a little too much information:
package com.example.pii;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/test")
public class TestResource {
@GET
@Path("/error")
@Produces(MediaType.APPLICATION_JSON)
public Response triggerError(@QueryParam("fail") boolean fail,
@QueryParam("name") String name,
@QueryParam("email") String email) {
if (fail) {
throw new MyCustomApplicationException(
"Operation failed for user %s (contact: %s). Internal check failed.".formatted(name, email));
}
return Response.ok("Operation successful!").build();
}
}
Scrubbing Secrets – PII Redaction Engine
Before we send anything to the AI, we clean it. Here’s our regex-based PII scrubbing service:
@ApplicationScoped
public class PiiRedactionEngine {
private static final Pattern EMAIL_PATTERN = Pattern.compile("\\b[\\w.-]+@[\\w.-]+\\.[a-z]{2,}\\b");
public String redact(String text) {
return EMAIL_PATTERN.matcher(text).replaceAll("[REDACTED_EMAIL]");
}
}
Yes, it’s basic. Yes, real-world redaction is harder. But this gives us a foundation.
The LangChain4j Guardrail
Quarkus LangChain4j supports input and output guardrails. We plug in our redactor here:
package com.example.pii;
import dev.langchain4j.data.message.AiMessage;
import io.quarkiverse.langchain4j.guardrails.OutputGuardrail;
import io.quarkiverse.langchain4j.guardrails.OutputGuardrailResult;
import jakarta.inject.Inject;
public class PiiRedactingUserMessageContentFilter implements OutputGuardrail {
@Inject
PiiRedactionEngine piiRedactionEngine;
@Override
public OutputGuardrailResult validate(AiMessage responseFromLLM) {
String content = responseFromLLM.text();
String redactedContent = piiRedactionEngine.redact(content);
if (!content.equals(redactedContent)) {
return reprompt(
"Response contains PII. Redacted content:", "Make sure to remove " + redactedContent);
}
return success();
}
}
The AI Service that Explains
Time to teach the AI to speak nicely to humans. Here's our interface from which Quarkus generates the implementation. Note the added OutputGuardrail:
package com.example.pii;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.guardrails.OutputGuardrails;
@RegisterAiService
public interface ErrorExplanationService {
@SystemMessage("""
You are an AI assistant that simplifies technical errors.
Respond briefly and clearly. Include the error ID if present.
""")
@UserMessage("Explain this error: {{errorMessage}} (Reference Error ID: {{errorId}})")
@OutputGuardrails(PiiRedactingUserMessageContentFilter.class)
String explainError(@V("errorMessage") String errorMessage, @V("errorId") String errorId);
}
The Exception Mapper with Brains
Here’s the final piece of the puzzle: an ExceptionMapper
that uses the AI to help the user, but keeps your secrets safe.
package com.example.pii;
import java.util.UUID;
import org.jboss.logging.Logger;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
@ApplicationScoped
public class MyCustomExceptionHandler implements ExceptionMapper<MyCustomApplicationException> {
@Inject
ErrorExplanationService ai;
private static final Logger LOG = Logger.getLogger(MyCustomExceptionHandler.class);
@Override
public Response toResponse(MyCustomApplicationException ex) {
var errorId = UUID.randomUUID().toString();
var original = ex.getMessage();
LOG.errorf(ex, "Error ID [%s]: %s", errorId, original);
String friendly;
try {
friendly = ai.explainError(original, errorId);
} catch (Exception e) {
LOG.error("AI failed to generate explanation", e);
friendly = "An unexpected error occurred. Reference ID: " + errorId;
}
return Response.status(500).entity(new ErrorResponse(errorId, friendly))
.type(MediaType.APPLICATION_JSON).build();
}
public record ErrorResponse(String errorId, String userMessage) {
}
}
Test It Like a Pro
./mvnw quarkus:dev
curl "http://localhost:8080/test/error?fail=true&name=John%20Smith&email=john@example.com"
You’ll see:
Full logs with PII (internal).
Redacted technical message in the response.
A friendly AI explanation likely phrased like a helpful support agent.
Epilogue: Security Isn’t Optional
“With great AI comes great responsibility.”
Limitations of Regex: The regex-based PII redaction in this tutorial is basic. Real-world PII detection is complex and may require sophisticated NLP tools, dedicated PII detection services (like Google Cloud DLP, Amazon Macie), or more comprehensive pattern libraries. Names, addresses, and context-specific PII are particularly hard for simple regex.
Comprehensive PII Strategy: This tutorial is a starting point. A robust PII strategy involves data discovery, classification, minimization, secure handling policies, and regular audits, not just redaction at one point.
Data Minimization: Only send the absolute minimum necessary information to the AI service, even after redaction.
AI Provider Trust & Data Policies: Always review your AI provider's data privacy, security, and usage policies. Understand how they handle the data you send.
Compliance: Adhere to relevant data protection regulations (GDPR, CCPA, HIPAA, etc.). Handling PII carries legal and ethical responsibilities.
Secure Internal Logging: The example logs the original error message with PII internally. Ensure your logging infrastructure (storage, access controls, retention policies) is secure and compliant. For highly sensitive environments, consider redacting or tokenizing PII even in internal logs.
False Positives/Negatives: PII redaction tools can have false positives (incorrectly redacting non-PII) or false negatives (missing actual PII). Test thoroughly and refine your redaction rules. Strive for a balance that protects privacy while maintaining usability.
Context for AI: Overly aggressive redaction might remove too much context, hindering the AI's ability to provide a useful explanation. This is a trade-off to manage.
Bonus Challenges for You
Add support for phone and SSN redaction.
Implement caching for repeated error messages.
Experiment with other, smaller local LLMs via Ollama.
You Did It
Congratulations! You've successfully built a Quarkus application with an intelligent exception handler that provides human-readable error messages using an AI service, while also implementing crucial PII guardrails.
Next time your users encounter a bug, they’ll thank you for the thoughtful message and your logs will have the full story.
And most importantly? You stayed secure.