Java Order Total Example: Validation and Overflow
Detect integer overflow in Java with Math.addExact and multiplyExact. See why a long result needs a widened operand before multiplication.
By Hardeep · · Updated

Integer overflow in Java can turn a valid-looking calculation into a wrong number without an exception. Use checked arithmetic where overflow is an error, and choose operand types before performing the calculation.
Overflow can happen before assignment to long
int price = 1_500_000_000;
int quantity = 2;
long wrong = price * quantity; // int multiplication overflows first
long widened = (long) price * quantity; // 3_000_000_000L
long checked = Math.multiplyExact((long) price, quantity);
// Math.multiplyExact(price, quantity) throws ArithmeticException here.
// Math.addExact(Integer.MAX_VALUE, 1) also throws.
The cast must occur before multiplication. Widening the already-overflowed result preserves the wrong value. Checked multiplication still matters for long operands when their product can exceed the long range. The Java Math documentation specifies the Exact methods' overflow exceptions. Choose how your application reports that failure rather than silently accepting a wrapped amount.
What Java is, and what the JDK provides
Java is a statically typed language: a variable has a declared or inferred type, and the compiler checks many invalid operations before execution. It supports object-oriented design, but calling it entirely object-oriented is misleading because it also has primitive values such as integers and booleans. You do not need a class hierarchy for every problem.
The Java Development Kit includes development tools such as the compiler. Compiled class files contain bytecode for a compatible Java Virtual Machine. This helps portability, but does not promise that every application works unchanged on every computer. Native libraries, filesystem assumptions, environment variables, and runtime versions can still differ. The official getting-started tutorial explains the compilation and execution model and the installation steps.
For a first exercise, check both java --version and javac --version. If one command is missing, fix the installation or executable search path before debugging application code. If they identify different installations, make the choice explicit in your editor and terminal. A working editor does not prove that a deployment machine has the same runtime.
Start with one calculation you can inspect
Suppose you need a subtotal for a small order. Represent the unit price in whole cents for this exercise, and make the quantity an integer. This avoids introducing binary floating-point rounding into a beginner example. A real payment system also needs currency rules, tax handling, rounding policy, and limits; this function deliberately addresses none of those requirements.
public class OrderExample {
static long subtotalCents(long unitPriceCents, int quantity) {
if (unitPriceCents < 0 || quantity < 0) {
throw new IllegalArgumentException("Price and quantity must be non-negative");
}
return Math.multiplyExact(unitPriceCents, quantity);
}
public static void main(String[] args) {
System.out.println(subtotalCents(1250, 3));
}
}
Save this as OrderExample.java. Compile with javac OrderExample.java, then run java OrderExample. The expected output is 3750. That is a prediction from the example, not a timing measurement. If compilation fails, read the first reported error before changing several lines at once; later messages can be consequences of that first mistake.
The method states its inputs and output in its signature. It rejects negative values instead of silently creating an invalid order. Checked multiplication makes overflow visible rather than wrapping to an unrelated result. These decisions illustrate a useful habit: define invalid input and failure behavior before adding a user interface.
Types help, but business rules still need tests
The compiler can distinguish a number from a string, but it cannot infer that a quantity must be positive in your particular business. An integer variable can hold a negative value. A string can be empty. A valid object reference can point to an order in the wrong state. Type checks are one layer of correctness, not a replacement for validation.
Extend the exercise with a short test matrix. Keep the expected result beside each input so another reader can reproduce the reasoning. Start with ordinary values, then examine boundaries and failure cases. This is more instructive than testing only the single example printed by the program.
| Input price and quantity | Expected result | Purpose |
|---|---|---|
| 1250 and 3 | 3750 | Ordinary multiplication |
| 1250 and 0 | 0 | Zero quantity boundary |
| -1 and 3 | IllegalArgumentException | Reject negative price |
| Long.MAX_VALUE and 2 | ArithmeticException | Expose overflow |
In a larger application, use a test runner and assertions so these checks run whenever code changes. Keep the calculation separate from network and database calls. A test of multiplication should not depend on a payment provider being online or on the contents of a developer's database.
Choose data structures from the question
Once an order has several lines, collections become useful. A list suits a sequence where position matters. A set represents distinct elements. A map associates keys with values, such as a product identifier with a quantity. The official learning index links to collections, generics, and object-model lessons when you need the detailed contracts.
Do not choose a collection only because it appeared in a tutorial. Ask whether duplicate entries are allowed, whether iteration order matters, and how a missing key should behave. Two lines for the same product might need merging, or they might represent different customizations. That is a domain decision, not something a map can decide for you.
Generics describe the kinds of values a collection holds. They help prevent accidental mixtures, but they do not make those values immutable or thread-safe. If several parts of a program share a mutable list, one can change what another observes. Passing a carefully chosen copy can be clearer than relying on an undocumented rule that callers must not edit it.
Handle errors where you can make a decision
A failed calculation, an unavailable database, and malformed user input require different responses. Catching every exception and returning zero would hide all three behind a plausible-looking value. Instead, let failures reach a layer that can decide whether to ask for corrected input, retry a transient operation, or stop the request.
The official exceptions guide explains checked and unchecked exceptions and resource handling. For your own methods, document what callers can reasonably recover from. Preserve the underlying cause when wrapping an exception, and avoid printing secrets or full personal records in diagnostic logs.
Files and database connections need lifecycle management even though Java has garbage collection. Use the appropriate resource-closing construct for resources that support it. An unused object becoming collectible does not establish when a connection will be returned to its pool.
Memory and concurrency require separate reasoning
Automatic memory management removes the need to manually free ordinary Java objects. It does not prevent you from retaining data forever. A static cache that keeps every order, or a listener that is never removed, can hold objects reachable long after they are useful. When investigating memory growth, distinguish retained application data from temporary allocations and runtime overhead.
Adding threads also does not automatically make an application faster. Shared state introduces ordering and coordination questions. For example, two requests can both read the last available item before either records a reservation. Correctness needs a design for that shared operation, often involving the database as well as application code.
Measure a representative workload before changing thread counts or runtime settings. Record input size, warmup, response times, and failure rate. A single fast execution on a laptop is not evidence that a service will remain responsive under concurrent traffic.
A practical learning sequence
First, run the small calculation and explain every line. Next, add the boundary checks from the table. Then accept several order lines and print a summary without introducing a web framework. After that, add a file input format and deliberately test a malformed row. Each step adds one new source of failure that you can identify and inspect.
Only then wrap the calculation in an application framework if your project needs one. Keep the core logic testable without starting a server. For database-backed work, the site's SQL indexing guide covers a separate question: how to evaluate the queries your application sends.
Java is a useful choice when its libraries, deployment model, and team knowledge match the work. It does not guarantee security, a particular salary, or superior performance. A stronger learning outcome is being able to explain a result, reproduce a failure, and change the program without breaking its stated rules.
Try a failure before adding a framework
Change the call to subtotalCents(-1, 3) and predict the exception. Then try subtotalCents(Long.MAX_VALUE, 2). These checks answer different questions: your rule rejects a negative price, while checked multiplication rejects a result that cannot fit. Keep both cases when you later move the calculation into a larger application.
Related reading
Send a correction with this article’s link and a supporting source.