
Complete migration of a widely-used v3.2.3 double-entry accounting system: parses and processes all ledger files, full query engine, 18 built-in plugins, CLI suite, up to 2.2× faster.
Kotlin Multiplatform implementation of Beancount v3.2.3, compatible with both Java and Kotlin.
Beancount JVM is a complete migration of Python Beancount accounting system to the JVM ecosystem using Kotlin Multiplatform (KMP). It maintains semantic compatibility with Python Beancount 3.2.3 while delivering up to 2.2x performance improvement on large ledgers.
@JvmStatic
Published to Maven Central and discoverable on klibs.io.
For KMP projects (commonMain):
// build.gradle.kts (KMP project)
commonMain.dependencies {
implementation("io.github.tonyzhye.beancount:core:3.2.3")
}For JVM-only projects:
// build.gradle.kts (JVM project)
dependencies {
implementation("io.github.tonyzhye.beancount:core-jvm:3.2.3")
}For Maven (JVM-only):
<dependency>
<groupId>io.github.tonyzhye.beancount</groupId>
<artifactId>core-jvm</artifactId>
<version>3.2.3</version>
</dependency>import io.github.tonyzhye.beancount.api.Beancount as bn
// Load a beancount file
val result = bn.loadFile("ledger.beancount")
// Check for errors
if (result.errors.isNotEmpty()) {
println(bn.formatErrors(result.errors))
return
}
// Query all transactions
val transactions = bn.getTransactions(result.entries)
// Get all accounts
val accounts = bn.getAccounts(result.entries)
// Build price map
val priceMap = bn.buildPriceMap(result.entries)
// Format entries back to beancount syntax
println(bn.formatEntries(result.entries))import io.github.tonyzhye.beancount.api.Beancount;
import io.github.tonyzhye.beancount.core.LoadResult;
// Load a beancount file
LoadResult result = Beancount.loadFile("ledger.beancount");
// Check for errors
if (!result.getErrors().isEmpty()) {
System.out.println(Beancount.formatErrors(result.getErrors()));
return;
}
// Get all accounts
var accounts = Beancount.getAccounts(result.getEntries());
System.out.println("Accounts: " + accounts);# Check a ledger file
beancount check ledger.beancount
# With verbose output
beancount check ledger.beancount --verbose
# Format/beautify a file
beancount format ledger.beancount -o formatted.beancount
# Run BQL queries
beancount query ledger.beancount "SELECT date, narration, position WHERE account ~ 'Expenses'"
# Show file dependencies
beancount deps ledger.beancount
# Get help for any command
beancount check --help┌─────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ bean-check | bean-doctor | bean-format | beanquery │
├─────────────────────────────────────────────────────────────┤
│ API Layer │
│ Beancount singleton (60+ methods) │
├─────────────────────────────────────────────────────────────┤
│ Query Engine │
│ BQL Parser → Compiler → Executor (60+ functions) │
├─────────────────────────────────────────────────────────────┤
│ Loader Layer │
│ Parse → Sort → Booking → Transformations → Validation │
├──────────────┬──────────────┬───────────────────────────────┤
│ Parser │ Plugins │ Core │
│ Lexer │ 18 Built-in │ 12 Directive Types │
│ Parser │ Plugins │ Amount / Cost / Posting │
│ Booking │ │ Inventory / Validation │
└──────────────┴──────────────┴───────────────────────────────┘
core (foundation)
↑
parser (depends on core)
↑
loader (depends on parser, core)
↑
api (depends on loader, parser, query, core, plugin-api)
↑
cli (depends on api, loader, query)
All 18 built-in plugins and the complete BQL query engine are implemented.
Compared against Python Beancount 3.2.3:
Full comparison report: CONSISTENCY_REPORT.md
Compared to Python Beancount 3.2.3 (load + book + validate):
| File Size | Python (ms) | Kotlin (ms) | Speedup |
|---|---|---|---|
| Example (340 KB) | 87 | 77 | 1.1x |
| 1 MB | 161 | 154 | 1.0x |
| 5 MB | 1,467 | 781 | 1.9x |
| 10 MB | 3,423 | 1,543 | 2.2x |
Kotlin scales better as file size grows — from roughly equivalent on small files to 2.2x faster on 10 MB ledgers.
Full benchmark report: PERFORMANCE_REPORT.md
# Build all modules
./gradlew build
# Run tests
./gradlew test
# Generate coverage report
./gradlew koverHtmlReport
# Build CLI distribution
./gradlew :cli:distZip
# Publish to local Maven
./gradlew publishToMavenLocalRecommended: IntelliJ IDEA with Kotlin plugin
settings.gradle.kts is)./gradlew test
# All tests
./gradlew test
# Specific module
./gradlew :core:jvmTest
./gradlew :parser:jvmTest
./gradlew :loader:jvmTest
./gradlew :query:jvmTest
# Coverage verification
./gradlew koverVerify
# Compatibility tests (requires Python + beancount 3.2.3)
./gradlew :loader:jvmTest --tests "*CompatTest"Coverage Status:
| Module | Coverage |
|---|---|
| core | 81.3% |
| parser | 81.6% |
| loader | 80.1% |
| query | 81.6% |
| api | 94.3% |
| plugin-api | 96.2% |
Contributions are welcome! Please read our Contributing Guide (if available) and ensure your changes:
./gradlew test)This project is licensed under the GNU General Public License v2.0 only (GPL-2.0-only).
Copyright (C) 2026 Tony Ye
Based on Beancount by Martin Blais, licensed under GPL-2.0-only.
This is an independent project, not affiliated with the official Beancount project.
Kotlin Multiplatform implementation of Beancount v3.2.3, compatible with both Java and Kotlin.
Beancount JVM is a complete migration of Python Beancount accounting system to the JVM ecosystem using Kotlin Multiplatform (KMP). It maintains semantic compatibility with Python Beancount 3.2.3 while delivering up to 2.2x performance improvement on large ledgers.
@JvmStatic
Published to Maven Central and discoverable on klibs.io.
For KMP projects (commonMain):
// build.gradle.kts (KMP project)
commonMain.dependencies {
implementation("io.github.tonyzhye.beancount:core:3.2.3")
}For JVM-only projects:
// build.gradle.kts (JVM project)
dependencies {
implementation("io.github.tonyzhye.beancount:core-jvm:3.2.3")
}For Maven (JVM-only):
<dependency>
<groupId>io.github.tonyzhye.beancount</groupId>
<artifactId>core-jvm</artifactId>
<version>3.2.3</version>
</dependency>import io.github.tonyzhye.beancount.api.Beancount as bn
// Load a beancount file
val result = bn.loadFile("ledger.beancount")
// Check for errors
if (result.errors.isNotEmpty()) {
println(bn.formatErrors(result.errors))
return
}
// Query all transactions
val transactions = bn.getTransactions(result.entries)
// Get all accounts
val accounts = bn.getAccounts(result.entries)
// Build price map
val priceMap = bn.buildPriceMap(result.entries)
// Format entries back to beancount syntax
println(bn.formatEntries(result.entries))import io.github.tonyzhye.beancount.api.Beancount;
import io.github.tonyzhye.beancount.core.LoadResult;
// Load a beancount file
LoadResult result = Beancount.loadFile("ledger.beancount");
// Check for errors
if (!result.getErrors().isEmpty()) {
System.out.println(Beancount.formatErrors(result.getErrors()));
return;
}
// Get all accounts
var accounts = Beancount.getAccounts(result.getEntries());
System.out.println("Accounts: " + accounts);# Check a ledger file
beancount check ledger.beancount
# With verbose output
beancount check ledger.beancount --verbose
# Format/beautify a file
beancount format ledger.beancount -o formatted.beancount
# Run BQL queries
beancount query ledger.beancount "SELECT date, narration, position WHERE account ~ 'Expenses'"
# Show file dependencies
beancount deps ledger.beancount
# Get help for any command
beancount check --help┌─────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ bean-check | bean-doctor | bean-format | beanquery │
├─────────────────────────────────────────────────────────────┤
│ API Layer │
│ Beancount singleton (60+ methods) │
├─────────────────────────────────────────────────────────────┤
│ Query Engine │
│ BQL Parser → Compiler → Executor (60+ functions) │
├─────────────────────────────────────────────────────────────┤
│ Loader Layer │
│ Parse → Sort → Booking → Transformations → Validation │
├──────────────┬──────────────┬───────────────────────────────┤
│ Parser │ Plugins │ Core │
│ Lexer │ 18 Built-in │ 12 Directive Types │
│ Parser │ Plugins │ Amount / Cost / Posting │
│ Booking │ │ Inventory / Validation │
└──────────────┴──────────────┴───────────────────────────────┘
core (foundation)
↑
parser (depends on core)
↑
loader (depends on parser, core)
↑
api (depends on loader, parser, query, core, plugin-api)
↑
cli (depends on api, loader, query)
All 18 built-in plugins and the complete BQL query engine are implemented.
Compared against Python Beancount 3.2.3:
Full comparison report: CONSISTENCY_REPORT.md
Compared to Python Beancount 3.2.3 (load + book + validate):
| File Size | Python (ms) | Kotlin (ms) | Speedup |
|---|---|---|---|
| Example (340 KB) | 87 | 77 | 1.1x |
| 1 MB | 161 | 154 | 1.0x |
| 5 MB | 1,467 | 781 | 1.9x |
| 10 MB | 3,423 | 1,543 | 2.2x |
Kotlin scales better as file size grows — from roughly equivalent on small files to 2.2x faster on 10 MB ledgers.
Full benchmark report: PERFORMANCE_REPORT.md
# Build all modules
./gradlew build
# Run tests
./gradlew test
# Generate coverage report
./gradlew koverHtmlReport
# Build CLI distribution
./gradlew :cli:distZip
# Publish to local Maven
./gradlew publishToMavenLocalRecommended: IntelliJ IDEA with Kotlin plugin
settings.gradle.kts is)./gradlew test
# All tests
./gradlew test
# Specific module
./gradlew :core:jvmTest
./gradlew :parser:jvmTest
./gradlew :loader:jvmTest
./gradlew :query:jvmTest
# Coverage verification
./gradlew koverVerify
# Compatibility tests (requires Python + beancount 3.2.3)
./gradlew :loader:jvmTest --tests "*CompatTest"Coverage Status:
| Module | Coverage |
|---|---|
| core | 81.3% |
| parser | 81.6% |
| loader | 80.1% |
| query | 81.6% |
| api | 94.3% |
| plugin-api | 96.2% |
Contributions are welcome! Please read our Contributing Guide (if available) and ensure your changes:
./gradlew test)This project is licensed under the GNU General Public License v2.0 only (GPL-2.0-only).
Copyright (C) 2026 Tony Ye
Based on Beancount by Martin Blais, licensed under GPL-2.0-only.
This is an independent project, not affiliated with the official Beancount project.