All Java, Gradle, and property files MUST end with a newline character.
Target Java 17 language level, compiled with JDK 21.
Exceptions
The two layers use different exception systems. Know which layer you are in.
fdb-record-layer-* (record layer)
Exceptions extend RecordCoreException, which extends LoggableException (unchecked).
- The exception message MUST be a static string — no string interpolation or variables.
- Use
.addLogInfo(LogMessageKeys.KEY, value)to attach context. Include as much as needed to diagnose the error: subspace, primary key, record type, index name, etc.
// Wrong
throw new RecordCoreException("Found corrupt record: " + record.getPrimaryKey());
// Correct
throw new RecordCoreException("Found corrupt record")
.addLogInfo(LogMessageKeys.PRIMARY_KEY, record.getPrimaryKey())
.addLogInfo(LogMessageKeys.RECORD_TYPE, record.getRecordType());
fdb-relational-* (relational / SQL layer)
Exceptions are RelationalException (checked, extends Exception). Always pair with an
ErrorCode, which maps to a SQLSTATE-standard 5-digit code.
- Message must still be a static string.
- Attach context with
.addContext("key", value)— this travels with the exception for logging and is preserved when converting toSQLException.
// Minimal form
throw new RelationalException("Transaction rolled back", ErrorCode.SERIALIZATION_FAILURE);
// With context
throw new RelationalException("Unsupported type in column", ErrorCode.CANNOT_CONVERT_TYPE)
.addContext("columnName", colName)
.addContext("typeName", typeName);
At JDBC interface boundaries (where the signature throws SQLException), convert via
.toSqlException(), which produces a ContextualSQLException carrying the ErrorCode
as the SQLState and the context map for logging:
throw new RelationalException("Element is not of STRUCT type", ErrorCode.CANNOT_CONVERT_TYPE)
.toSqlException();
Specialized subclasses (InvalidColumnReferenceException, InvalidTypeException) exist for
common error cases — prefer them when they fit. InternalErrorException is deprecated; use
new RelationalException("...", ErrorCode.INTERNAL_ERROR) directly.
UncheckedRelationalException wraps a RelationalException as a RuntimeException for
contexts where checked exceptions cannot be thrown (e.g., inside lambdas). Unwrap with
.unwrap() to recover the original RelationalException.
Logging
Same principle as exceptions — structured, static messages:
- Always use
KeyValueLogMessage.of("static message", key, value, ...). - The message text must be a static string. Variables go in key/value pairs.
// Wrong
LOGGER.info("Unable to open file: " + filename);
// Correct
LOGGER.info(KeyValueLogMessage.of("Unable to open file", LogMessageKeys.FILENAME, filename));
Futures (async code)
The fdb-record-layer-* and lower-level parts of fdb-relational-core are fully asynchronous
via CompletableFuture. The JDBC-facing relational API presents a synchronous surface, but
async code is common in the implementation beneath it.
- Never call
join()orget()in production code. Usecontext.asyncToSync()ordatabase.asyncToSync()instead — they enforce timeouts, record wait timers, and handle error wrapping. - Never call any blocking method from inside a
CompletableFuturecompletion lambda. The thread pool is finite; blocking inside a future can deadlock the entire system. - Avoid
thenApplyAsync()/thenComposeAsync()unless the lambda does significant CPU work. These enqueue a new task on the executor for no benefit in the common case. When you do need them, always pass an explicitExecutor. - Always pass an
Executorto anyAsyncUtilorMoreAsyncUtilmethod that accepts one (e.g.AsyncUtil.whileTrue()). Never use the overload without an executor.
Checkstyle-banned imports
The authoritative list is in gradle/check.gradle (BANNED_IMPORTS).
Inline comments
Describe intent, not mechanics. Explain the why — a hidden constraint, a non-obvious invariant, a workaround for a specific FDB behavior. If removing the comment would not confuse a future reader, don't write it.
Javadoc
Add Javadoc for public APIs. It is not necessary for private methods unless the logic is genuinely non-obvious.