JFederation is a Java client library for the Federated Database protocol, providing a complete client-side implementation of the standard for Java applications. The implementation is based on the Open Federated Database specification, and the programming methodology is heavily based on the original implementation, FederationLib.
- Full implementation of the Federated Database standard, interacting with any OFD-compliant Federation server
- Content scanning with entity resolution, classification, and risk assessment
- Complete coverage of the server API: operators, records, evidence, reports, blacklist records, file attachments, audit logs, search, and server information
- Pagination, category filters, and sorting built into every listing and search endpoint
- Thread-safe client design,
FederationClientinstances can be shared across threads - All errors are mapped to a single checked exception carrying the HTTP status code and server message
- Java 24 or newer — the library is compiled with
--release 24 - Maven 3.8+ to build the project and run the test suite
- A running Federation server for anything other than constructing a client, the reference implementation is FederationLib, which includes a dockerized server deployment
Dependencies are resolved from Maven Central: OkHttp for HTTP transport, Jackson for JSON serialization, and SLF4J for logging (Logback is bundled as the runtime implementation).
The project is hosted on GitHub, so Maven projects can pull
the library directly from the repository using JitPack,
JitPack compiles and caches the library automatically on the first resolution, no publishing is
required. This is useful for testing unreleased builds from the master branch.
Add the JitPack repository and the dependency to your pom.xml:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.nosial</groupId>
<artifactId>JFederation</artifactId>
<version>master-SNAPSHOT</version>
</dependency>
</dependencies>The master-SNAPSHOT version always resolves to the latest commit on the master branch. To pin a
specific version, tag the repository (for example v1.0.0) and use the tag name as the version.
To use the library without JitPack, build and install it into your local Maven repository:
mvn clean installThen add the dependency to your project using the local coordinates:
<dependency>
<groupId>net.nosial</groupId>
<artifactId>jfederation</artifactId>
<version>1.0.0</version>
</dependency>All functionality is exposed through a single entry point, the FederationClient class, every method
maps to a REST endpoint of the Federation server, and handles serialisation, authentication, and error
mapping internally.
A client is constructed with the base URL of the Federation server and an optional operator access token, clients without a token operate anonymously against the server's public endpoints:
import net.nosial.jfederation.FederationClient;
// Anonymous client
FederationClient client = new FederationClient("https://federation.example.com");
// Authenticated client with an operator access token
FederationClient client = new FederationClient("https://federation.example.com", "access-token");The access token can be changed at any time during the lifetime of the client:
client.setAccessToken("new-token"); // switch authentication
client.setAccessToken(null); // revert to anonymous accessception; authentication and authorisation errors bubble up with the 401/403 status codes of the response. Clients implement AutoCloseable` and should be closed when no longer needed to release the
underlying HTTP connection pool:
client.close();For custom HTTP settings (timeouts, proxies, TLS, etc.) an existing OkHttpClient can be passed to
the constructor instead:
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
FederationClient client = new FederationClient("https://federation.example.com", "access-token", httpClient);Server information, including the server name, API version, feature flags (which record types are
publicly visible), and total record counts, is available through getServerInformation(), the raw
OpenAPI specification of the server is available through getSpecification():
ServerInformation info = client.getServerInformation();
System.out.printf("Server %s, API version %s%n", info.serverName(), info.apiVersion());
System.out.printf("Known entities: %d, open reports: %d%n", info.knownEntities(), info.reports());Content scanners accept a ContentInput or a list of inputs. Each input can carry text, an
operator note, tag, confidentiality flag, and metadata; at least one input must contain text:
| Method (omit optional parameters) | Return Type | Description |
|---|---|---|
scanContent(content) |
ScannedContent |
Convenience overload for a single text input |
scanContent(ContentInput, author, topK, threshold) |
ScannedContent |
Scans one typed content input |
scanContent(List<ContentInput>, author, topK, threshold) |
ScannedContent |
Scans one or more typed content inputs |
Here's an example of scanning content and inspecting the classification result:
ScannedContent scan = client.scanContent(
new ContentInput("Suspicious message to inspect"), "author@example.com", null, null);
ContentClassification classification = scan.getClassification();
System.out.printf("Classification %s with %.1f%% confidence, detected language: %s%n",
classification.classificationFlag(),
classification.confidence() * 100.0,
classification.detectedLanguage());The global search queries all record types with an optional type filter, the search query must be at least 2 characters:
// Search all record types
List<SearchResult> results = client.search("author@example.com");
// Search only entity and blacklist records, page 1, 25 per page
List<SearchResult> results = client.search("author@example.com",
List.of(RecordType.ENTITY, RecordType.BLACKLIST), 1, 25);SearchResult records provide a typed getRecord() method that resolves the underlying record based
on the matched type.
Operators are the accounts of the Federation server, all data-modifying functionality is performed by an operator, and each operator carries granular permissions dictating what they are allowed to do.
| Method | Description |
|---|---|
createOperator(name) |
Creates a new operator, returns the raw access token (only exposed at creation time) |
getOperator(uuid) / getSelf() |
Returns an operator record / the currently authenticated operator |
listOperators(page, limit, category, by, order) |
Lists operators with optional filters |
searchOperators(query, page, limit, category, by, order) |
Searches operators by name |
updateOperatorName(uuid, name) |
Updates the display name of an operator |
deleteOperator(uuid) |
Deletes an operator |
disableOperator(uuid) / enableOperator(uuid) |
Disables / enables an operator account |
setOperatorPermissions(uuid, enabled) |
Sets whether the operator has operator-level permissions |
setClientPermissions(uuid, enabled) |
Sets whether the operator has client-level permissions |
setManagementPermissions(uuid, enabled) |
Sets whether the operator has management-level permissions |
generateAccessToken(update) |
Refreshes the current operator's token, optionally updating the client |
generateOperatorAccessToken(uuid) |
Refreshes another operator's token |
listOperatorAuditLogs(uuid, ...) |
Lists the audit log entries of an operator |
listOperatorEvidence(uuid, ...) |
Lists the evidence an operator submitted |
listOperatorBlacklist(uuid, ...) |
Lists the blacklist records created by an operator |
listOperatorReports(uuid, ...) / listAssignedOperatorReports(uuid, ...) |
Lists the reports submitted by / assigned to an operator |
Here's an example of creating an operator and generating a new token:
OperatorCreated created = client.createOperator("my-application");
System.out.println("Operator UUID: " + created.uuid());
// The raw token is only returned once at creation time, store it securely
String token = created.accessToken();
String refreshed = client.generateAccessToken(true); // refresh and update this clientEntities are the subjects tracked by the federation; users, domains, IP addresses, or any other identifier present in scanned content:
| Method | Description |
|---|---|
getEntityRecord(identifier) |
Returns the record of an entity by identifier |
queryEntity(identifier) |
Returns an entity's relationship group and active blacklist state |
searchEntities(query, page, limit, category, by, order) |
Searches entities by identifier |
listEntities(page, limit, category, by, order) |
Lists entities |
getTopThreats(limit) |
Lists the highest-risk entities |
updateEntity(identifier, metadata) |
Updates the metadata of an entity |
deleteEntity(identifier) |
Deletes an entity |
setEntityWhitelist(identifier, whitelisted) |
(Un)whitelists an entity, exempting it from scans |
clearEntityReputation(identifier) |
Clears the accumulated reputation of an entity |
setEntityRelationship(identifier, relatedEntityIdentifier, type) |
Sets the relationship between two entities |
clearEntityRelationship(identifier) |
Clears the relationship of an entity |
pushEntity(host, identifier, metadata) |
Pushes an entity to another federation server |
listEntityAuditLogs(identifier, ...) |
Lists the audit log entries involving an entity |
listEntityBlacklistRecords(identifier, ...) |
Lists the blacklist records against an entity |
listEntityEvidenceRecords(identifier, ...) |
Lists the evidence submitted against an entity |
listEntityReports(identifier, ...) |
Lists the reports submitted against an entity |
Record listings of entities are EntityRecord objects carrying the identifier, risk score,
classification flag, relationship hierarchy, and metadata of the entity.
Evidence records are the supporting data attached to entities, either when submitting a report or manually by operators, and can hold text content, attachments, tags, notes, and confidentiality flags:
| Method | Description |
|---|---|
submitEvidence(entityIdentifier) |
Creates an empty evidence record for an entity |
submitEvidence(identifier, text, note, tag, confidential, metadata) |
Submits evidence with all optional fields |
listEvidence(page, limit, includeConfidential, category, by, order) |
Lists evidence records |
searchEvidence(query, page, limit, category, by, order) |
Searches evidence records |
getEvidenceRecord(uuid) |
Returns a single evidence record |
getEvidenceAttachments(uuid) |
Lists the attachments of an evidence record |
updateEvidenceConfidentiality(uuid, confidential) |
Updates the confidentiality flag of evidence |
updateEvidenceTag(uuid, tag) |
Updates the tag of evidence |
addEvidenceToReport(evidenceUuid, reportUuid) |
Attaches evidence to a report |
deleteEvidence(uuid) |
Deletes an evidence record |
Confidential evidence is hidden from unauthenticated clients by default, listing endpoints
support an includeConfidential parameter to include them when the operator has the appropriate
permissions.
Reports are the central mechanism of the federation server, submitted against an entity, they group one or more pieces of evidence and can be classified, assigned to operators, and closed:
| Method | Description |
|---|---|
submitReport(entity, ContentInput, incidentType, message) |
Submits one evidence item with a report |
submitReport(entity, List<ContentInput>, incidentType, message) |
Submits one or more evidence items with a report |
listReports(page, limit, category, by, order) |
Lists reports |
listOpenedReports(page, limit, by, order) |
Lists reports that are not yet closed |
searchReports(query, page, limit, category, by, order) |
Searches reports by content |
getReport(uuid) |
Returns a single report |
listReportEvidenceRecords(reportUuid, ...) |
Lists evidence associated with a report |
closeReport(uuid) / closeReport(uuid, classification) |
Closes a report, optionally with a classification |
assignOperatorToReport(reportUuid, operatorUuid) |
Assigns an operator to a report |
deleteReport(uuid) |
Deletes a report |
submitReport returns a ReportSubmission object with typed access to the created report
(getReport()) and its List<EvidenceRecord> (getEvidence()). Upload attachments separately
against an evidence record.
Blacklist records are the server's mitigation mechanism, an operator can condemn an entity to the blacklist of the server with an incident type and a report backing the decision:
| Method | Description |
|---|---|
blacklistEntity(identifier, reportUuid, type) |
Blacklists an entity permanently |
blacklistEntity(identifier, reportUuid, type, expires) |
Blacklists an entity with an expiration timestamp (Unix epoch seconds) |
listBlacklistRecords(page, limit, includeLifted, category, by, order) |
Lists blacklist entries |
searchBlacklist(query, page, limit, category, by, order) |
Searches blacklist entries |
getBlacklistRecord(uuid) |
Returns a single blacklist record |
liftBlacklistRecord(uuid) |
Lifts the blacklist entry of an entity |
extendBlacklistRecord(uuid, seconds) |
Extends the blacklist of an entity by the given seconds |
deleteBlacklistRecord(uuid) |
Deletes a blacklist record |
Evidence records support file attachments with uploads from disk or URL, and note attachments (plain-text content):
| Method | Description |
|---|---|
uploadFileAttachment(evidenceUuid, localFilePath) |
Uploads a local file attachment |
uploadFileAttachment(evidenceUuid, localFilePath, fileName) |
Uploads a local file attachment with a custom name |
uploadFileAttachmentFromUrl(evidenceUuid, fileUrl) |
Uploads an attachment from a reachable URL |
uploadFileAttachmentFromUrl(evidenceUuid, fileUrl, maxFileSize) |
Uploads an attachment from a URL with a size limit |
uploadNoteAttachment(evidenceUuid, fileName, content) |
Uploads a plain-text note attachment |
downloadAttachment(attachmentUuid, directoryPath) |
Downloads an attachment into the given directory |
getAttachmentInfo(uuid) |
Returns the metadata of an attachment |
listAttachments(page, limit, category, by, order) |
Lists attachments |
searchAttachments(query, page, limit, category, by, order) |
Searches attachments |
deleteAttachment(uuid) |
Deletes an attachment |
File uploads default to a maximum file size of 50 MB, matching the FederationLib client default, the limit can be raised per-request for URL uploads.
Every listing and search endpoint accepts the same pagination and sorting arguments: page (1-based)
and limit (minimum 1), followed by an optional category filter, by (whatever field to sort by,
case-insensitive), and order ("ASC" or "DESC"). Every combination has a matching overload,
so the calling convention of each method grows gradually, for example:
// Defaults: page 1, limit 100
List<EntityRecord> entities = client.listEntities();
// Explicit pagination
List<EntityRecord> entities = client.listEntities(2, 50);
// Pagination with a category filter, sorted by risk descending
List<EntityRecord> entities = client.listEntities(2, 50, "HIGH_RISK", "risk_score", "DESC");None of the argument orderings change between endpoints, once the pattern is memorized the entire API becomes predictable.
All failures are thrown as FederationClientException, carrying the HTTP status code of the
response and, when the server returns one, its error message:
try
{
client.getEntityRecord("non-existent-entity");
}
catch (FederationClientException e)
{
System.out.printf("Request failed with status %d: %s%n", e.getStatusCode(), e.getMessage());
}Invalid call parameters (empty content, tokens containing whitespace, queries shorter than 2
characters, zero page/limit values, etc.) are rejected by the client immediately with an
IllegalArgumentException; authentication and authorisation errors bubble up with the 401/403
status codes of the response.
The test suite runs against a live Federation server, and the repository includes a
docker-compose.yml that brings up the reference
FederationLib server, MariaDB,
and Redis on http://localhost:7000:
docker compose up -d
mvn testBy default the tests expect the server at http://localhost:7000 with access token
abcdefghijklmnopqrstuvwxyz123456 (matching the compose defaults). The test suite also can be
configured through the SERVER_ENDPOINT and SERVER_ACCESS_TOKEN environment for testing against
a different server:
SERVER_ENDPOINT=http://localhost:7000 SERVER_ACCESS_TOKEN=my-token mvn testThis project is licensed under the MIT License - see the LICENSE file for details.