Validators and a retry-until-valid guard for LLM output, in Java. The Java port of guardflow (Rust) / guardflow-go — same idea, adapted to Java: no framework dependency, just a Handler (String -> String) decorated the way a Servlet Filter decorates a request handler.
Not published to Maven Central — pull it straight from GitHub via JitPack:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.thaicn1712</groupId>
<artifactId>guardflow-java</artifactId>
<version>main-SNAPSHOT</version> <!-- or a tagged release -->
</dependency>Guard guard = Guard.create()
.with(new NotEmpty())
.with(new MaxLength(2000));
Handler guarded = GuardFlow.withGuard(myLlmHandler, guard, 3); // up to 3 attempts
String output = guarded.run("what does the fox say?");myLlmHandler is any Handler — String run(String input) throws Exception, the shape most LLM call wrappers in Java already have. No interface to implement beyond that, no framework to adopt.
NotEmpty, MaxLength, MinLength, MatchesRegex, OneOf, ValidJson, NoPii (email/phone pattern match), Profanity (word-list match), Truncate (never fails outright — offers a truncated fix instead).
Some validators (like Truncate) can repair what they'd otherwise reject. Guard.fix applies that repair and re-checks:
Guard guard = Guard.create().with(new Truncate(280));
Guard.FixResult result = guard.fix(tooLongText); // result.text() truncated to 280 code points, result.outcome().passed() == trueFor checks that need network access — a moderation API, an LLM-as-judge call — implement AsyncValidator instead of Validator, and use AsyncGuard / GuardFlow.withAsyncGuard. It's a plain blocking call rather than CompletableFuture chaining: on modern Java, run it on a virtual thread and blocking costs nothing.
class ModerationCheck implements AsyncValidator {
public ValidationOutcome validate(String text) throws Exception {
var result = client.check(text);
return result.flagged()
? ValidationOutcome.fail(result.reason())
: ValidationOutcome.pass();
}
}
AsyncGuard guard = AsyncGuard.create().with(new ModerationCheck());
Handler guarded = GuardFlow.withAsyncGuard(myLlmHandler, guard, 3);mvn -q compile exec:java -Dexec.mainClass=io.github.thaicn1712.guardflow.examples.RetryUntilValidExampleMIT