Checkmate is a fluent Java library for validating runtime conditions with styled, aligned output and built-in short-circuiting. The name is both a nod to the chess term and a play on "mate" as in friend or helper ("check mate").
It supports rich semantics like warnings, fallback values, and exception propagation, and is ideal for configuration validation, deployment assertions, CLI diagnostics or an alternative to some uses of java.util.Optional
.
<dependency>
<groupId>org.tomitribe</groupId>
<artifactId>checkmate</artifactId>
<version>1.0</version>
</dependency>
Checkmate supports multiple styles of checking depending on the use case. Each provides a fluent, readable way to validate logic and format output. Choose based on your context: imperative, fluent, test-like, or functional.
This style gives you full control over the logic and messaging. Instantiate a Check
, run your validation, then call .pass()
, .fail()
, or .error()
accordingly.
final File file = new File("/opt/java");
final Check check = checks.check(file + " exists");
if (file.exists()) {
check.pass();
} else {
check.fail();
}
You can also report exceptions:
try {
someAction();
check.pass();
} catch (IOException e) {
check.error(e);
}
Simple and concise. Best used when the condition is already evaluated:
checks.check("File exists", () -> file.exists());
checks.check("Optional config", () -> config.isPresent(), WARN);
Use checks.object("label", object)
for multiple validations on the same object. Checks short-circuit after the first failure.
checks.object("JAVA_HOME", new File("/opt/java"))
.check("exists", File::exists)
.check("is directory", File::isDirectory)
.check("is readable", File::canRead)
.check("is writable", File::canWrite);
The above may generate output like the following:
JAVA_HOME exists . . . . . . . . . . . . . . . . PASS JAVA_HOME is directory . . . . . . . . . . . . . PASS JAVA_HOME bin readable . . . . . . . . . . . . . SKIP JAVA_HOME bin writable . . . . . . . . . . . . . SKIP
Declare the object inline and return it only if all checks pass.
final File dir = checks.object("JAVA_HOME", new File("/opt/java"))
.check("exists", File::exists)
.check("is directory", File::isDirectory)
.check("can read", File::canRead)
.getOrThrow(() -> new IllegalStateException("Invalid JAVA_HOME"));
Use .map()
to chain checks to derived objects. Skips downstream checks if prior ones fail.
final File bin = checks.object("JAVA_HOME", dir)
.check("exists", File::exists)
.check("is directory", file -> false)
.map("bin directory", file -> new File("bin"))
.check("exists", File::exists)
.check("is file", File::isFile)
.check("can read", File::canRead)
.getOrThrow(() -> new RuntimeException("Invalid JAVA_HOME/bin/ directory"));
Output:
JAVA_HOME exists. . . . . . . . . . . . . . . . . PASS JAVA_HOME is directory . . . . . . . . . . . . . FAIL
Use .or(…)
to try alternative objects if validation fails.
final File javaHome = checks.object("JAVA_HOME", new File("/opt/java"))
.check("exists", File::exists)
.check("is directory", File::isDirectory)
.or("JAVA_HOME (fallback #1)", new File("/usr/lib/jvm/java-11-openjdk"))
.check("exists", File::exists)
.check("is directory", File::isDirectory)
.or("JAVA_HOME (fallback #2)", new File("/usr/java/latest"))
.check("exists", File::exists)
.check("is directory", File::isDirectory)
.or("JAVA_HOME (fallback #3)", new File(System.getProperty("java.home")))
.check("exists", File::exists)
.check("is directory", File::isDirectory)
.getOrThrow(() -> new IllegalStateException("No valid JAVA_HOME found"));
Output:
JAVA_HOME exists. . . . . . . . . . . . . . . . . FAIL JAVA_HOME is directory. . . . . . . . . . . . . . SKIP JAVA_HOME (fallback #1) exists. . . . . . . . . . FAIL JAVA_HOME (fallback #1) is directory. . . . . . . SKIP JAVA_HOME (fallback #2) exists. . . . . . . . . . PASS JAVA_HOME (fallback #2) is directory . . . . . . PASS
To enable output:
final Checks checks = Checks.builder()
.print(System.out, 50)
.build();
To disable output:
final Checks checks = Checks.builder().build();
This is useful when you’re using .getOrThrow()
or .result()
to control flow and don’t want logging.
You can implement your own output using CheckLogger
.
public class Slf4jLogger implements CheckLogger {
private final Logger logger = LoggerFactory.getLogger("checkmate");
@Override
public Check log(final String name) {
return new Check() {
public void pass() { logger.info("{} PASS", name); }
public void fail() { logger.error("{} FAIL", name); }
public void warn() { logger.warn("{} WARN", name); }
public void skip() { logger.debug("{} SKIP", name); }
public void fail(String reason) { logger.error("{} FAIL {}", name, reason); }
public void warn(String reason) { logger.warn("{} WARN {}", name, reason); }
public void error(String reason) { logger.error("{} ERROR {}", name, reason); }
};
}
}
Register the logger:
final Checks checks = Checks.builder()
.logger(new Slf4jLogger())
.build();