Should You Store Money as Long or BigDecimal in Java?

The article explains why floating‑point types are unsafe for monetary values, compares the Long‑in‑cents and BigDecimal‑in‑yuan approaches, lists their advantages and drawbacks, and provides concrete Java code, database schemas, layer‑boundary conversion patterns, and common pitfalls.

java1234
java1234
java1234
Should You Store Money as Long or BigDecimal in Java?

Why double and float are unsuitable for money

Using double for amounts looks convenient but leads to precision errors because binary floating‑point cannot represent decimal fractions exactly. For example,

double price1 = 0.1; double price2 = 0.2; System.out.println(price1 + price2);

prints 0.30000000000000004, causing mismatched cents in accounting.

Two correct strategies

The author outlines two reliable ways to store monetary values in Java:

Store the smallest unit (cents) as a Long integer.

Store the amount in yuan using BigDecimal with controlled scale.

Long‑in‑cents solution

All calculations use integers where 1 yuan = 100 cents . The utility class provides conversion and arithmetic methods:

/**
 * Money utility – Long cents version
 */
public class MoneyUtils {
    /** Yuan to fen: 1.23 yuan → 123 fen */
    public static long yuanToFen(String yuan) {
        return new BigDecimal(yuan)
                .multiply(new BigDecimal("100"))
                .longValue();
    }
    /** Fen to yuan: 123 fen → "1.23" */
    public static String fenToYuan(long fen) {
        return BigDecimal.valueOf(fen, 2).toPlainString();
    }
    /** Add two fen values with overflow check */
    public static long add(long fen1, long fen2) {
        return Math.addExact(fen1, fen2);
    }
}

Database column example:

CREATE TABLE t_order (
    id BIGINT PRIMARY KEY,
    amount_fen BIGINT NOT NULL COMMENT 'order amount (cents)',
    create_time DATETIME NOT NULL
);

Advantages : no precision loss, excellent performance, low memory, aligns with third‑party payment APIs that also use cents.

Disadvantages : requires manual yuan↔cent conversion, cumbersome for interest, exchange‑rate, or other fractional calculations.

BigDecimal‑in‑yuan solution

BigDecimal is designed for exact decimal arithmetic, ideal for financial, interest, and exchange‑rate scenarios.

/**
 * Money utility – BigDecimal version
 */
public class BigDecimalMoneyUtils {
    private static final int SCALE = 2; // keep two decimal places
    /** Safe factory – avoid double constructor */
    public static BigDecimal of(String amount) {
        return new BigDecimal(amount).setScale(SCALE, RoundingMode.HALF_UP);
    }
    /** Add two amounts */
    public static BigDecimal add(BigDecimal a, BigDecimal b) {
        return a.add(b).setScale(SCALE, RoundingMode.HALF_UP);
    }
    /** Apply discount: price × rate */
    public static BigDecimal applyDiscount(BigDecimal price, BigDecimal rate) {
        return price.multiply(rate).setScale(SCALE, RoundingMode.HALF_UP);
    }
}

Database column example:

CREATE TABLE t_finance_detail (
    id BIGINT PRIMARY KEY,
    amount DECIMAL(18,2) NOT NULL COMMENT 'amount (yuan)',
    create_time DATETIME NOT NULL
);

Advantages : intuitive representation (yuan), precise control over rounding, suitable for interest, exchange‑rate, and allocation calculations.

Disadvantages : larger object overhead, slightly slower under high concurrency, must keep scale and RoundingMode consistent.

Choosing the right approach

Use the following guideline:

High‑throughput transactions, orders, wallet balances → Long (cents).

Financial statements, interest, exchange‑rate calculations → BigDecimal (yuan).

Front‑end display → convert to a string in yuan and avoid JavaScript floating‑point arithmetic.

Practical layered pattern

Keep conversions at system boundaries. Example service stores incoming yuan strings as cents, persists them, and converts back to strings for API responses:

@Service
public class OrderService {
    /** Create order: front‑end sends "99.99" yuan, internally store 9999 cents */
    public OrderVO createOrder(CreateOrderRequest req) {
        long amountFen = MoneyUtils.yuanToFen(req.getAmountYuan());
        Order order = new Order();
        order.setAmountFen(amountFen);
        orderMapper.insert(order);
        OrderVO vo = new OrderVO();
        vo.setAmountYuan(MoneyUtils.fenToYuan(amountFen));
        return vo;
    }
}

Request DTO uses String to prevent JSON deserialization into a floating‑point type:

public class CreateOrderRequest {
    /** Amount in yuan, e.g. "99.99" */
    private String amountYuan;
    // getters / setters
}

Common pitfalls

Never instantiate new BigDecimal(0.1); use the string constructor or BigDecimal.valueOf to avoid hidden binary errors.

Compare monetary BigDecimal values with compareTo, not equals, because scale differences make equals return false.

Multiplying two Long cent values can overflow; perform the multiplication with BigDecimal and convert back to long if needed.

Never use JavaScript Number for money calculations; use integer cents or a decimal library such as decimal.js.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaBigDecimalFinancialLongMonetaryPrecision
java1234
Written by

java1234

Former senior programmer at a Fortune Global 500 company, dedicated to sharing Java expertise. Visit Feng's site: Java Knowledge Sharing, www.java1234.com

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.