IRCafe is a Java 25 desktop chat client with a Swing UI and a Spring Boot backend, supporting IRC, Quassel Core, and Matrix backends.
* Light Theme
* Dark Theme
./gradlew bootRunThis launches the Swing app and loads runtime config from ${XDG_CONFIG_HOME}/ircafe/ircafe.yml (or ~/.config/ircafe/ircafe.yml).
- Multi-server connections with add/edit/manage server dialogs.
- TLS and SASL (
AUTO,PLAIN,SCRAM-SHA-256,SCRAM-SHA-1,EXTERNAL,ECDSA-NIST256P-CHALLENGE). - Auto-reconnect with backoff + jitter, plus heartbeat timeout detection.
- Global SOCKS5 proxy plus per-server proxy override (auth, remote DNS, connect/read timeouts, proxy test).
- Auto-join channels or PM targets and per-server perform-on-connect commands.
- Rich slash command support for connection/session control, channel/user ops, invite workflows, monitor, ignore/soft-ignore, CTCP, DCC, CHATHISTORY, filters, and raw IRC.
- Matrix transport backend with homeserver probe + token-authenticated connect flow.
- Room and DM messaging, room joins/leaves, room directory/listing, and sync-based timeline ingestion.
- Matrix-backed compose features mapped into IRCafe flows, including reply/react/edit/redact, typing, read markers, and history navigation.
- Matrix support is active and tested, but full slash-command parity with IRC is still in progress.
- Capability negotiation with per-capability runtime toggles.
- Broad IRCv3 coverage including
message-tags,server-time,echo-message,standard-replies,labeled-response,typing,read-marker,multiline,batch,chathistory,znc.in/playback, plus compose-related capabilities (draft/reply,draft/react,message-edit,message-redaction). - IRCv3
stspolicy learning and persistence, with automatic TLS/port upgrades on later connects. - Message reply/reaction/edit/redaction flows in chat UI and command layer.
- Typing indicators (send + receive) and read-marker updates.
- ZNC and soju network discovery with ephemeral server entries, per-network auto-connect preferences, and optional save of discovered entries to the main server list.
- Docked chat workspaces with per-buffer state.
- Multiple buffers can be open on screen simultaneously.
- Unread + highlight counters in the server tree.
- Presence folding (join/part/quit/nick condensation).
- Transcript context menu actions (reply, react, redact, inspect, must be enabled on the server).
- Find-in-transcript support, load-older controls, and history context actions.
- Command history, nick completion, and alias expansion.
- Nick context menu actions for query/whois/ctcp, moderation, DCC, and ignore toggles.
- CTCP request routing options and configurable CTCP auto-replies (
VERSION,PING,TIME).
- Theme system with FlatLaf, DarkLaf, curated IntelliJ themes, optional full IntelliJ pack list, and live preview.
- Accent overrides, chat palette controls (timestamp/system/mention), mention strength, and chat font family/size controls.
- FlatLaf tweak controls for density and corner radius, plus optional global UI font override.
- Preferences tabs for appearance, startup, tray/notifications, chat, CTCP replies, IRCv3, embeds/previews, history/storage, notifications, commands, diagnostics, filters, network, and user lookups.
- Persistent per-server monitor nick list with
/monitorand/mon, plus a dedicated monitor panel. - Automatic monitor resync on connect; ISON fallback polling when MONITOR is unavailable.
- Hard ignore and soft ignore lists (with CTCP behavior toggles).
- WeeChat-style render-time filters with:
- Scope matching
- Direction/kind/source/tag/text matching
- Global defaults and per-scope overrides
- Filter placeholders/hints with collapse behavior and history-batch controls.
- Optional embedded HSQLDB logging with Flyway-managed schema.
- Remote-only history mode when DB logging is disabled.
- Logging controls for PM logging, soft-ignored line logging, and retention.
- History loading integrates local DB data with IRCv3 CHATHISTORY and ZNC playback remote fill.
- Duplicate suppression in storage paths (message-id and exact-match checks where applicable).
- Per-server Log Viewer node with async search/export (off-EDT), sortable columns, and configurable column visibility.
- Log Viewer filters: nick, message text, hostmask, channel, date range, and protocol/server-event suppression controls.
- Per-server interceptor system with a dedicated
Interceptorstree group. - Multiple interceptor definitions, each with:
- Server scope (
this serverorany server) - Channel include/exclude rules (
All,None,Like,Glob,Regex) - Trigger rules by event type plus message/nick/hostmask matching
- Server scope (
- Action pipeline per interceptor:
- Status bar notification
- Desktop toast
- Sound (built-in or custom file)
- External script execution
- Captured hits table and per-server hit counts shown in the tree.
- System tray integration (close-to-tray, minimize-to-tray, start minimized).
- Desktop notifications for highlights, private messages, and connection state.
- Notification sound selection (built-in and custom).
- Event-driven notification rules (kicks, bans, invites, modes, joins/parts, PM/notice/CTCP, topic changes, netsplit, etc.).
- Rule actions can route to toast, status bar, sound, notifications node, script, and optional Pushy forwarding.
- Rule cooldown plus toast dedupe/rate-limiting behavior to reduce spam.
- A number of built-in sounds to choose from, or customize with your own.
- Optional inline image embedding for direct image links (
png,jpg,gif,webp). - Optional animated GIF playback.
- Link previews (OpenGraph/oEmbed + dedicated resolvers for sites such as Wikipedia, YouTube, Slashdot, IMDb, Rotten Tomatoes, X, Instagram, Imgur, GitHub, Reddit, Mastodon, and major news pages).
- DCC chat/file workflows (
/dcc ...,/dccmsg) plus a DCC Transfers panel with action hints and quick follow-up actions.
- Channel List node for
/listresults with filtering and double-click join. - User command alias editor with HexChat
commands.confimport. - Monitor and Notifications nodes per server for quick operational actions.
- Runtime diagnostics surfaces under
Application(Unhandled Errors, AssertJ Swing, jHiccup, Inbound Dedup, JFR, Spring, Terminal), including bounded event feeds plus clear/export actions.
- Java 25 for local run/build tasks (
./gradlew bootRun,./gradlew test,./gradlew jpackage,java -jar ...). - GNU Make + Docker only if using the optional Makefile Docker shortcuts.
- Flatpak +
flatpak-builderonly if building Flatpak bundles on Linux.
- Entry point:
src/main/java/cafe/woden/ircclient/IrcSwingApp.java src/main/java/cafe/woden/ircclient/ui: Swing UI, docking, dialogs, rendering, tray integration.src/main/java/cafe/woden/ircclient/app: orchestration, mediator flow, command dispatch, notifications.src/main/java/cafe/woden/ircclient/irc: IRC/Matrix/Quassel protocol integrations and capability handling.src/main/java/cafe/woden/ircclient/config: runtime config models, property binding, registries.src/main/java/cafe/woden/ircclient/logging: log persistence, history ingestion, log viewer services.src/main/java/cafe/woden/ircclient/net: TLS/proxy bootstrapping and lightweight HTTP utilities.src/test/java/cafe/woden/ircclient: unit + integration + architecture tests.src/functionalTest/java/cafe/woden/ircclient: Swing functional tests (*FunctionalTest).
The repository includes a root Makefile with convenience commands that call Gradle directly.
make help
make build
make test
make checkIf you prefer a Make-first workflow, these cover most tasks:
| Workflow | Make | Gradle equivalent |
|---|---|---|
| Run app from source | make bootrun |
./gradlew bootRun |
| Build runnable jar | make jar |
./gradlew bootJar |
| Run lint checks | make lint |
./gradlew lint |
| Run unit/non-functional tests | make test |
./gradlew test |
| Run integration tests | make integration-test |
./gradlew integrationTest |
| Run architecture guardrails | make architecture-test |
./gradlew architectureTest |
| Run Swing functional tests | make functional-test |
./gradlew functionalTest |
| Build app image | make jpackage |
./gradlew jpackage |
| Run full verification | make check |
./gradlew check |
Run any Gradle task(s) via:
make gradle TASKS="bootRun"
make gradle TASKS="integrationTest --tests 'cafe.woden.ircclient.irc.QuasselCoreContainerIntegrationTest'"Build/test in Docker (no local JDK install required):
make docker-image
make docker-build
make docker-check
make docker-gradle TASKS="test"
# Also available: make docker-lint, make docker-integration-test, make docker-architecture-test, make docker-functional-testNotes:
- Local Make targets default
GRADLE_USER_HOMEto./.gradle-local(override withLOCAL_GRADLE_USER_HOME=/path). - Docker shortcuts run
./gradlewinside a local image built from versionedDockerfile.build. Dockerfile.buildpinseclipse-temurin:25-jdkby digest for reproducible toolchain resolution.- Docker targets auto-build the image if missing (override with
DOCKER_IMAGE=...if needed). - A persistent repo-local cache directory (
./.gradle-docker) is used for Docker Gradle cache reuse between runs.
From the project root:
./gradlew bootRun
# or:
make bootrunThis launches the Swing UI.
./gradlew bootJar
# or:
make jar
java -jar build/libs/*.jar./gradlew jpackage
# or:
make jpackageOutput is written under build/dist/ using the platform's app-image layout.
On Linux, the generated app image also includes:
IRCafe.desktopinstall.shuninstall.sh
You can customize the Linux desktop launcher install prefix used in generated desktop entries:
./gradlew jpackage -PappInstallDir=/opt/IRCafeTo install just the desktop entry for the current user on Linux:
./gradlew installLinuxDesktopEntryInstall tooling (example for Debian/Ubuntu):
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepoBuild the app image and Flatpak repo/bundle:
./gradlew jpackage generateFlatpakMetainfo
flatpak-builder --force-clean --repo=build/flatpak/repo --default-branch=stable --install-deps-from=flathub build/flatpak/workdir packaging/flatpak/cafe.woden.IRCafe.yml
flatpak build-bundle build/flatpak/repo build/dist/ircafe.flatpak cafe.woden.IRCafe stable --runtime-repo=https://flathub.org/repo/flathub.flatpakrepoOn tagged GitHub releases, CI now publishes ircafe-<version>-linux-x64.flatpak as a release asset.
Common local checks:
./gradlew lint
./gradlew test
./gradlew checkOne-time contributor setup:
./scripts/setup-git-hooks.shThis enables the repo's commit-msg hook for Conventional Commit validation.
Targeted suites:
# Spring/context integration tests from src/test (classes ending with IntegrationTest)
./gradlew integrationTest
# Modulith/jMolecules/ArchUnit guardrails
./gradlew architectureTest
# Swing UI functional tests from src/functionalTest (classes ending with FunctionalTest)
./gradlew functionalTestQuassel support integration matrix:
QuasselCoreRealServerIntegrationTest: validate against an already-running Quassel Core.QuasselCoreContainerIntegrationTest: validate containerized Quassel bootstrap/connect/sync.QuasselCoreContainerNetworkE2eIntegrationTest: validate Quassel + real ircd network flow.- All Quassel integration tests are opt-in and gated by
quassel.it.*properties.
Real Quassel Core integration (opt-in):
# Validates live Quassel Core connect/auth/sync, lag probe heartbeat, disconnect, reconnect.
# Requires a reachable Quassel Core and credentials.
QUASSEL_IT_LOGIN=alice QUASSEL_IT_PASSWORD=secret \
./gradlew integrationTest --tests 'cafe.woden.ircclient.irc.QuasselCoreRealServerIntegrationTest' \
-Dquassel.it.enabled=true \
-Dquassel.it.host=127.0.0.1 \
-Dquassel.it.port=4242 \
-Dquassel.it.login=alice \
-Dquassel.it.password=secretOptional knobs (env vars or -D properties): quassel.it.server-id, quassel.it.tls,
quassel.it.nick, quassel.it.real-name.
Containerized Quassel Core integration via Testcontainers (also opt-in):
# Starts Quassel Core in Docker and validates setup/connect/sync/heartbeat/reconnect.
./gradlew integrationTest --tests 'cafe.woden.ircclient.irc.QuasselCoreContainerIntegrationTest' \
-Dquassel.it.container.enabled=trueOptional knobs: quassel.it.container.image, quassel.it.container.login,
quassel.it.container.password, quassel.it.container.startup-timeout-seconds.
Default image is pinned to linuxserver/quassel-core:0.14.0 for amd64 compatibility.
Expanded two-container Quassel E2E (Quassel Core + local ngIRCd):
# Validates Quassel create/connect network flow plus live channel message ingress.
./gradlew integrationTest --tests 'cafe.woden.ircclient.irc.QuasselCoreContainerNetworkE2eIntegrationTest' \
-Dquassel.it.container.e2e.enabled=trueOptional knobs: quassel.it.container.e2e.quassel-image,
quassel.it.container.e2e.irc-image, quassel.it.container.e2e.channel,
quassel.it.container.e2e.message, quassel.it.container.e2e.login,
quassel.it.container.e2e.password.
Note: this test skips when the selected Quassel image does not expose runtime
network creation to remote clients.
Direct IRC backend E2E (Pircbotx + local ngIRCd, no Quassel):
# Validates direct IRC backend connect/ready/join/inbound+outbound messages/reconnect.
./gradlew integrationTest --tests 'cafe.woden.ircclient.irc.PircbotxContainerNetworkE2eIntegrationTest' \
-Dirc.it.container.e2e.enabled=trueOptional knobs: irc.it.container.e2e.irc-image, irc.it.container.e2e.channel,
irc.it.container.e2e.bot-message, irc.it.container.e2e.app-message,
irc.it.container.e2e.nick, irc.it.container.e2e.login.
Matrix Synapse container integration (opt-in):
# Starts a local Synapse container and validates probe/whoami/send/sync/history flows.
./gradlew integrationTest --tests 'cafe.woden.ircclient.irc.matrix.MatrixSynapseContainerIntegrationTest' \
-Dmatrix.it.container.enabled=trueOptional knobs (property/env): matrix.it.container.image / MATRIX_IT_CONTAINER_IMAGE,
matrix.it.container.server-name / MATRIX_IT_CONTAINER_SERVER_NAME,
matrix.it.container.http-port / MATRIX_IT_CONTAINER_HTTP_PORT,
matrix.it.container.startup-timeout-seconds / MATRIX_IT_CONTAINER_STARTUP_TIMEOUT_SECONDS,
matrix.it.container.registration-secret / MATRIX_IT_CONTAINER_REGISTRATION_SECRET.
When to run which tests:
- Spring Modulith/jMolecules/ArchUnit or module-boundary refactors:
./gradlew architectureTest test - Spring wiring/integration changes (new beans, events, transactional boundaries):
./gradlew integrationTest - Swing UI behavior changes (dialogs, tree interactions, panel flows, rendering):
./gradlew functionalTest test - Non-UI feature work:
./gradlew test - Pre-merge on larger changes:
./gradlew checkplus relevant targeted suites above
Auto-fix workflow (Error Prone auto-corrects first, then Spotless formatting):
./gradlew spotlessApply
# Run Error Prone auto-corrects, then Spotless lint check
./gradlew spotlessLint
# Optional: override which Error Prone patch checks are applied
./gradlew spotlessApply -PerrorPronePatchChecks=CheckReturnValue,ReturnValueIgnoredAdditional verification/reporting tasks:
./gradlew mutationTest
./gradlew themeValidate
./gradlew dependencyCheckAnalyze
./gradlew cyclonedxBom/helpshows the main command groups (including backend-specific helpers)./help dccshows DCC chat/file commands./help chathistory,/help markread, and/help uploadshow focused usage for those flows./help edit,/help redact, and/help deleteshow IRCv3 compose mutation command help.- Quassel-specific workflows:
/quasselsetupand/quasselnet .... /filter helpshows local filter command usage./monitor helpshows monitor command usage.
When running IRCafe (from source or as a packaged jar), edit the runtime config file (loaded on startup and overrides the defaults in application.yml):
- Default path:
${XDG_CONFIG_HOME}/ircafe/ircafe.ymlwhenXDG_CONFIG_HOMEis set. - Otherwise falls back to:
~/.config/ircafe/ircafe.yml(i.e.${user.home}/.config/ircafe/ircafe.yml)
To use a different location, set ircafe.runtime-config (environment variable: IRCAFE_RUNTIME_CONFIG) or pass it on the command line:
IRCAFE_RUNTIME_CONFIG=/path/to/ircafe.yml java -jar build/libs/ircafe-*.jar
# or:
java -jar build/libs/ircafe-*.jar --ircafe.runtime-config=/path/to/ircafe.ymlServer definitions (IRC, Quassel Core, Matrix) use the irc.servers section.
IRC example:
irc:
servers:
- id: "libera"
host: "irc.libera.chat"
port: 6697
tls: true
# Optional: IRC PASS / server password (commonly needed for bouncers like ZNC)
# ZNC examples: "username:password" or "username/network:password"
serverPassword: "${IRCAFE_SERVER_PASSWORD:}"
nick: "${IRCAFE_NICK:IRCafeUser}"
login: "${IRCAFE_IDENT:ircafe}"
realName: "${IRCAFE_REALNAME:IRCafe User}"
sasl:
enabled: false
username: "${IRCAFE_SASL_USERNAME:}"
password: "${IRCAFE_SASL_PASSWORD:}"
mechanism: "PLAIN"
autoJoin:
- "#irclient"Matrix example:
irc:
servers:
- id: "matrix-main"
backend: "matrix"
# Host can be a plain host or a full base URL (for example with a path prefix).
host: "https://matrix.example.org"
port: 443
tls: true
# Matrix access token (preferred here). IRCafe also falls back to sasl.password if this is blank.
serverPassword: "${IRCAFE_MATRIX_ACCESS_TOKEN:}"
# Optional local display identity used by IRCafe UI defaults.
nick: "@ircafe:example.org"
login: "ircafe"
realName: "IRCafe Matrix"
# Optional auto-join targets (room id or alias).
autoJoin:
- "!someroom:example.org"
- "#ircafe:example.org"You can also override UI behavior under the ircafe.ui prefix. For inline image previews (direct image links in chat),
this is disabled by default:
ircafe:
ui:
imageEmbedsEnabled: falseYou can toggle this at runtime from the GUI: Settings -> Preferences… -> Inline images.
Other runtime settings (theme and appearance tweaks, tray/notifications, CTCP replies, IRCv3 capability toggles, STS cache entries, monitor nick lists, filters, command aliases, interceptors, notification rules, logging preferences, and more) are persisted to the runtime config file as you change them in the UI.
The default application.yml uses these environment variables:
IRCAFE_NICK(defaults toIRCafeUser)IRCAFE_IDENT(defaults toircafe)IRCAFE_REALNAME(defaults toIRCafe User)IRCAFE_CTCP_VERSION(defaults toIRCafe)IRCAFE_SERVER_PASSWORD(no default) — IRCPASS/ server password (useful for ZNC:username:passwordorusername/network:password)IRCAFE_SASL_USERNAME(no default)IRCAFE_SASL_PASSWORD(no default)
You can set them for a single run:
IRCAFE_NICK=myNick ./gradlew bootRunOr export them in your shell:
export IRCAFE_NICK=myNick
export IRCAFE_IDENT=myIdent
export IRCAFE_REALNAME="My IRC Client"To enable SASL, set irc.servers[].sasl.enabled: true in your config and set IRCAFE_SASL_USERNAME and IRCAFE_SASL_PASSWORD.
- Runtime config and UI state default to
${XDG_CONFIG_HOME}/ircafe/ircafe.ymlwhenXDG_CONFIG_HOMEis set, otherwise~/.config/ircafe/ircafe.yml. - With logging enabled (default
ircafe.logging.hsqldb.nextToRuntimeConfig: true), HSQLDB files (for exampleircafe-chatlog.*) are stored next to the runtime config file. - Linux
uninstall.shremoves the installed app image and desktop entry, but does not remove user config/history data.
- Keep
irc.client.tls.trustAllCertificates: falseunless you are intentionally testing with self-signed certs in a trusted environment. - Interceptor and notification rule script actions execute local scripts. Only use scripts and paths you trust.
- System tray and desktop notification behavior depends on OS/session support.
- Linux supports D-Bus notification actions when enabled (
ircafe.ui.tray.linuxDbusActionsEnabled: true). - Linux app-image builds include desktop/install helper files; other platforms keep standard app-image packaging.
- Commit messages follow Conventional Commits:
<type>(<scope>): <subject>. - Local hooks are configured by
./scripts/setup-git-hooks.sh; CI validates the same rules. - Allowed types:
feat,fix,refactor,perf,test,docs,build,ci,chore,style,revert.
Please feel free to file an issue, or get in touch on Libera.Chat channel #ircafe
GPL-3.0. See LICENSE.