Joda Money — Spec vs Reality
Spec source: README.md
Scope: Source code only — infrastructure config files (Docker, CI/CD, YAML) not analysed
Coverage: ██████████ 8/8 features fully implemented
Feature Coverage
| Feature | Spec Intent | Code Reality | Status |
|---|---|---|---|
| Money parsing | Create a monetary value using parse, e.g., Money.parse("USD 23.87") | Money.parse(String) exists as a factory method that parses a string of the form 'CCC amount' where CCC is a three-let... | ✅ Implemented |
| Money creation from double | Create a Money from CurrencyUnit and double, e.g., Money.of(usd, 12.43d) | Money.of(CurrencyUnit, double) exists and converts the double via BigDecimal.valueOf before calling of(CurrencyUnit, ... | ✅ Implemented |
| Money addition | Add another amount, e.g., money.plus(Money.of(usd, 12.43d)) | Money.plus(Money) and other plus methods exist, performing addition with currency validation. | ✅ Implemented |
| Money subtraction of major units | Subtract an amount in dollars (major units), e.g., money.minusMajor(2) | Money.minusMajor(long) exists and subtracts the specified major units. | ✅ Implemented |
| Money multiplication with rounding | Multiply by a factor with rounding, e.g., money.multipliedBy(3.5d, RoundingMode.DOWN) | Money.multipliedBy(double, RoundingMode) exists, performing multiplication with specified rounding. | ✅ Implemented |
| Money comparison | Compare two amounts, e.g., money.isGreaterThan(dailyWage) | Money.isGreaterThan(Money) and other comparison methods exist, delegating to compareTo. | ✅ Implemented |
| Money currency conversion | Convert to another currency using a supplied rate, e.g., money.convertedTo(CurrencyUnit.GBP, conversionRate, Rounding... | Money.convertedTo(CurrencyUnit, BigDecimal, RoundingMode) exists, converting the amount using the provided rate. | ✅ Implemented |
| BigMoney conversion | Use BigMoney for more complex calculations, e.g., money.toBigMoney() | Money.toBigMoney() exists and returns the underlying BigMoney instance. | ✅ Implemented |
What the code does that the spec never mentioned
These behaviors exist in the codebase but have no entry in any spec document. They represent implicit engineering decisions — security contracts, undocumented constraints, behavioral choices — that the spec author either assumed, forgot, or decided after writing the spec.
1. Android zero bug workaround
BigMoney.of(CurrencyUnit, double) returns zero for a zero double value to avoid a bug in stripTrailingZeros() on Android versions before v30.
Evidence: BigMoney.java:L262
Why it matters: This platform-specific behavior could cause unexpected rounding behavior differences on Android vs. other platforms.
2. System property data provider
CurrencyUnit static initializer loads a CurrencyUnitDataProvider specified by system property 'org.joda.money.CurrencyUnitDataProvider', defaulting to DefaultCurrencyUnitDataProvider. If a SecurityException occurs, it falls back to default.
Evidence: CurrencyUnit.java:L50
Why it matters: This allows external configuration of currency data, which could be exploited if the system property is set maliciously, potentially loading incorrect currency definitions.
3. Scale constraint on creation
Money.of(CurrencyUnit, BigDecimal) throws ArithmeticException if the scale of the amount exceeds the currency's decimal places, enforcing strict scale validation. An overload with RoundingMode allows rounding.
Evidence: Money.java:L226
Why it matters: This strict validation may surprise users expecting automatic rounding, potentially causing runtime errors if not handled.
4. Multi-attempt signed parsing
SignedPrinterParser.parse attempts to parse using three formatters (positive, zero, negative) and selects the result with the highest parse index, preferring non-error contexts.
Evidence: SignedPrinterParser.java:L60
Why it matters: This parsing strategy can lead to unexpected results when multiple formatters succeed, potentially selecting a wrong amount if formatting is ambiguous.
Security Observations
Behaviors with security implications that are not documented in the spec. These are not CVEs — they are undocumented security contracts, auth edge cases, and trust-boundary decisions found directly in the code.
🔴 Provider loading via system property HIGH
CurrencyUnit static initializer loads a data provider specified by system property 'org.joda.money.CurrencyUnitDataProvider'. An attacker with access to system properties could supply a malicious provider to inject false currency data.
Evidence: CurrencyUnit.java:L50
Risk: If an attacker can set this system property (e.g., via -D flag or environment), they could load arbitrary currency definitions. This could lead to incorrect conversions, validation bypass, or denial of service.
🟡 Classpath resource loading MEDIUM
DefaultCurrencyUnitDataProvider.registerCurrencies() calls loadFromFiles(), which reads all resources with a given name from the classpath using ClassLoader.getResources(). If an attacker can manipulate the classpath, they could supply malicious CSV files.
Evidence: DefaultCurrencyUnitDataProvider.java:L59
Risk: An attacker controlling the classpath could inject fake currency data, leading to incorrect monetary behavior across the application.
🟢 No input sanitization in parse LOW
The parse method in AmountPrinterParser reads characters from the text and converts localized digits and signs into a BigDecimal. There is no explicit validation of the string format beyond the parsing logic.
Evidence: AmountPrinterParser.java:L129
Risk: Malicious or malformed input could cause parsing errors or produce unexpected amounts, but the parsing is well-defined and rejects invalid inputs via error index.
Questions for the Engineering Team
- Why does BigMoney.of(double) include an Android-specific workaround for zero values, and how is this tested across platforms?
- What are the exact parsing rules for Money.parse()? Is it strictly format 'CCC amount' or more lenient?
- How does the CurrencyUnit data provider loading handle environments with security managers that restrict system property access?
- Why does Money.hashCode return money.hashCode() + 3 instead of just delegating?
- What is the rationale behind strict scale validation in Money.of(CurrencyUnit, BigDecimal) rather than automatically rounding?
Generated by Verifiably — every finding is grounded in file:line evidence from the codebase, not training data.