Recommended resources
The freely available resources this lesson will direct you to.
Last time, you established a key distinction for tracing Java programs: variables hold either primitive values or reference values, and method arguments are always copied by value. Numeric expressions add another layer: Java may convert operand values before it evaluates an operation, then convert the result again when assigning it.
This lesson focuses on predicting those conversions precisely. It is especially important when moving from JavaScript: ordinary JavaScript Number arithmetic is floating-point, while Java keeps int, long, float, and double operations distinct. An expression that appears numerically harmless can overflow, truncate, or lose precision depending on its operand types.
By the end, you should be able to explain widening and narrowing conversions, identify an expression’s promoted type, predict integer overflow, and handle Java floating-point edge cases in an interview.
1. Numeric types set the boundaries
Java’s integral primitives have fixed widths and ranges:
| Type | Width | Range |
|---|---|---|
byte |
8 bits | to |
short |
16 bits | to |
int |
32 bits | to |
long |
64 bits | to |
char |
16 bits | to |
The signed integral types use two’s-complement representation. char is the exception: it is an unsigned UTF-16 code unit, not a small signed integer.
float and double are binary floating-point types based on IEEE 754:
floatis 32-bit binary floating point.doubleis 64-bit binary floating point.- Decimal fractions such as
0.1often cannot be represented exactly in either type.
Two literal rules appear frequently in interview snippets:
int count = 42; // Integer literals are int by default
long total = 42L; // L makes the literal long
double rate = 2.5; // Decimal literals are double by default
float tax = 2.5F; // F makes the literal float
Without the F suffix, this does not compile:
// float tax = 2.5; // Error: double cannot be assigned to float
The Java Language Specification’s type ranges and arithmetic rules are the reliable foundation for the rest of the lesson.
Chapter 4. Types, Values, and Variables - Oracle Help Center
Read the Oracle Java Language Specification to anchor the fixed ranges of integral types and the rule that integer operations occur in either int or long precision.
In Section 4.2.1, review the ranges from integral type ranges. Then, in Section 4.2.2, read the explanation beginning “If an integer operator other than a shift operator...” through the example near the end. Focus on the distinction between an operation’s type and the type of the variable that receives its result.
2. Widening conversion: permitted automatically, but not always exact
A widening primitive conversion moves a value to a type that Java permits without an explicit cast.
| Source type | Types it can widen to implicitly |
|---|---|
byte |
short, int, long, float, double |
short |
int, long, float, double |
char |
int, long, float, double |
int |
long, float, double |
long |
float, double |
float |
double |
For example:
byte retries = 12;
int attempts = retries; // Widening: valid
int requestCount = 500;
long totalRequests = requestCount; // Widening: valid
Do not reduce this rule to “a smaller type fits in a larger type.” That shortcut fails to explain important cases:
charcan widen toint, butshortcannot widen tochar.intcan widen tofloat, even though someintvalues cannot be represented exactly asfloat.longcan widen todouble, yet many largelongvalues lose low-order bits.
For integral-to-integral widening, the value is preserved exactly. But widening to a floating-point type may preserve the overall magnitude while losing exact precision.
int original = 16_777_217;
float converted = original;
System.out.println(converted); // 1.6777216E7, representing 16_777_216
The conversion is legal and does not throw an exception. However, float has insufficient precision to distinguish every integer at that magnitude.
Chapter 5. Conversions and Contexts
Read the JLS definitions of widening and narrowing conversions. These rules are more precise than the informal “small type to big type” explanation often used in introductory material.
In Section 5.1.2, read from the precision discussion, paying particular attention to int to float and long to double. Then continue into Section 5.1.3 and read the low order bits rule. The latter explains surprising results such as casting 257 to byte.
Interview Q: Does widening always preserve a value exactly?
Answer: No. Widening between integral types preserves the value exactly, and float to double is also exact. But int to float, long to float, and long to double can lose precision because floating-point types have limited significant bits.
3. Narrowing conversion: explicit cast, possible information loss
A narrowing conversion moves to a type with a smaller range or lower precision. Java normally requires an explicit cast because the conversion may lose information.
int port = 8080;
short shortPort = (short) port; // Explicit narrowing conversion
double ratio = 9.8;
int roundedDown = (int) ratio; // 9, not 10
For integral narrowing, Java retains only the low-order bits needed by the target type. It does not clamp a value to the nearest valid boundary.
int value = 257;
byte narrowed = (byte) value;
System.out.println(narrowed); // 1
A byte represents 256 distinct bit patterns. The low 8 bits of 257 are the same as the low 8 bits of 1, so the result is 1.
System.out.println((byte) 128); // -128
System.out.println((byte) 255); // -1
System.out.println((byte) 256); // 0
System.out.println((byte) 257); // 1
The conversion does not throw an exception.
Constant expressions are a useful exception
Java allows a constant int expression to be assigned to byte, short, or char without a cast when the value is within range.
byte maxRetries = 127; // Valid: constant value fits in byte
short year = 2025; // Valid: constant value fits in short
// byte invalid = 128; // Compile-time error: value does not fit
But a variable has a declared type, not merely a currently convenient value:
int configuredRetries = 12;
// byte retries = configuredRetries; // Compile-time error
byte retries = (byte) configuredRetries; // Explicit acknowledgement of risk
Even though configuredRetries currently contains 12, it could hold any int at runtime. The compiler requires you to make narrowing intentional.
Floating-point to integer narrowing
Casting a finite floating-point value to an integer truncates toward zero:
System.out.println((int) 5.9); // 5
System.out.println((int) -5.9); // -5
It does not round to the nearest integer. Use an explicit rounding operation when that is what the domain needs:
long rounded = Math.round(5.9); // 6
There are further edge cases worth knowing:
System.out.println((int) Double.NaN); // 0
System.out.println((int) Double.POSITIVE_INFINITY); // 2147483647
System.out.println((int) Double.NEGATIVE_INFINITY); // -2147483648
For a double or float outside the target integral range, Java produces the nearest representable int or long boundary. It does not throw an exception.
A concise casting and promotion demonstration may help consolidate the mechanics:
Watch “Type Conversion in Java” by Telusko for a concrete walkthrough of explicit casts, byte-range loss, and arithmetic promotion.
Watch explicit casts for narrowing from integral and floating-point values. Then watch byte narrowing for the 257 to byte example. Finally, watch type promotion to see why arithmetic on byte operands produces an int.
4. Numeric promotion: determine the operation type first
The most common interview mistake is to look at the target variable before determining the type used by the operation.
Java promotes operands before evaluating most numeric operators. For binary arithmetic such as +, -, *, /, and %, the promoted type follows this order:
- If either operand is
double, evaluate asdouble. - Otherwise, if either operand is
float, evaluate asfloat. - Otherwise, if either operand is
long, evaluate aslong. - Otherwise, evaluate as
int.
In particular, byte, short, and char are promoted to int for arithmetic.
byte left = 10;
byte right = 20;
int sum = left + right; // Valid: expression type is int
// byte sum = left + right; // Compile-time error: int cannot become byte implicitly
The two operands start as byte, but left + right is an int expression.
The same applies to unary operators:
byte value = 7;
int negated = -value; // Unary numeric promotion: result is int
Read the formal promotion rules now; their structure is exactly what interviewers are testing when they mix operand types.
Chapter 5. Conversions and Contexts
This section formalizes the promotion ladder used in arithmetic, comparisons, array indices, and numeric conditional expressions.
In Section 5.6, “Numeric Contexts,” read the full rule set beginning with numeric promotion rules. Focus first on arithmetic contexts: double takes priority, then float, then long, with int as the default for the remaining integral types.
Mixed operands
int records = 3;
long batches = 4L;
double multiplier = 1.5;
long first = records + batches; // long
double second = batches * multiplier; // double
The target variable does not change how the arithmetic is carried out:
long incorrect = 1_000_000 * 1_000_000;
long correct = 1_000_000L * 1_000_000;
System.out.println(incorrect); // -727379968
System.out.println(correct); // 1000000000000
In the first assignment, both operands are int, so multiplication happens in 32-bit int arithmetic and overflows before the result is widened to long.
In the second assignment, the L suffix makes one operand a long. Java promotes the other operand to long, so multiplication occurs in 64-bit arithmetic.
Compound assignment has an implicit narrowing conversion
This is another classic contrast:
short quantity = 10;
// quantity = quantity + 1; // Compile-time error: result is int
quantity += 1; // Valid
System.out.println(quantity); // 11
quantity += 1 effectively includes a narrowing conversion back to short. That convenience also means it can silently overflow:
short max = 32_767;
max += 1;
System.out.println(max); // -32768
Do not interpret += as merely shorter syntax. Its conversion behavior differs from writing the arithmetic and assignment separately.
Integer division follows the operation type
System.out.println(5 / 2); // 2
System.out.println(-5 / 2); // -2
System.out.println(5 / 2.0); // 2.5
With two integral operands, division discards the fractional part toward zero. With a double operand, Java performs floating-point division.
5. Integer overflow wraps silently
An integer type has a finite range. When arithmetic exceeds that range, Java retains the low-order bits of the mathematical result. This is commonly described as wrapping modulo for int and modulo for long.
int maximum = Integer.MAX_VALUE;
System.out.println(maximum); // 2147483647
System.out.println(maximum + 1); // -2147483648
There is no exception and no warning at runtime. The result is Integer.MIN_VALUE.
byte counter = 127;
counter++;
System.out.println(counter); // -128
This behavior matters in backend systems when calculating sizes, IDs, time units, counters, pagination offsets, or monetary minor units. Choosing long instead of int increases the available range, but it does not eliminate overflow.
When overflow would indicate a defect rather than valid wraparound behavior, use the exact arithmetic methods:
int total = Math.addExact(Integer.MAX_VALUE, 1);
// Throws ArithmeticException
Similar methods include Math.subtractExact, Math.multiplyExact, Math.incrementExact, and Math.toIntExact.
Integer division by zero is a separate issue:
int divisor = 0;
// int result = 10 / divisor; // Throws ArithmeticException
That exception is caused by division by zero, not overflow. Ordinary integer overflow itself is silent.
Interview Q: Will assigning an overflowing expression to long fix it?
Answer: Not if the expression was already evaluated as int. In long x = 1_000_000 * 1_000_000, both operands are int, so overflow happens before assignment. Make at least one operand long, for example with 1_000_000L.
Interview Q: Does Java throw an exception on integer overflow?
Answer: No. byte, short, int, and long operations wrap according to fixed-width two’s-complement arithmetic. Use Math.addExact and related methods when overflow must be detected.
6. Floating-point arithmetic: precision, infinity, and NaN
Floating-point arithmetic has different failure modes from integer arithmetic. It approximates real-number calculations using finite binary representations.
This familiar expression is not reliably true:
System.out.println(0.1 + 0.2 == 0.3); // false
The literals are approximations in binary floating point. The two sides of the comparison can therefore differ slightly.
This is why binary float and double are poor default choices for currency calculations. For money, use an integer minor unit when appropriate, such as cents, or use BigDecimal constructed from a decimal string when decimal arithmetic is required by the domain.
Java floating-point values also include special values:
System.out.println(1.0 / 0.0); // Infinity
System.out.println(-1.0 / 0.0); // -Infinity
System.out.println(0.0 / 0.0); // NaN
Unlike integer division, floating-point division by zero does not throw ArithmeticException.
NaN, meaning “Not a Number,” has intentionally unusual comparison behavior:
double invalid = 0.0 / 0.0;
System.out.println(invalid == invalid); // false
System.out.println(invalid != invalid); // true
System.out.println(invalid < 10); // false
System.out.println(invalid > 10); // false
System.out.println(Double.isNaN(invalid)); // true
Use Double.isNaN(value) or Float.isNaN(value) rather than equality to test for NaN.
Java also distinguishes positive and negative zero in some operations:
System.out.println(0.0 == -0.0); // true
System.out.println(1.0 / 0.0); // Infinity
System.out.println(1.0 / -0.0); // -Infinity
The zeros compare equal, but division can reveal their signs.
Chapter 4. Types, Values, and Variables - Oracle Help Center
Read the relevant JLS material for the exact semantics of IEEE 754 values, NaN comparisons, signed zero, and floating-point promotion.
In Section 4.2.3, read the special-value introduction, then continue through the paragraphs on signed zero and NaN comparisons. In Section 4.2.4, read the floating-point operation rule and the two following paragraphs on double and float promotion.
Interview Q: Why is 0.1 + 0.2 == 0.3 false?
Answer: double uses finite binary floating-point representations. Values such as 0.1 and 0.2 cannot generally be represented exactly, so calculations may produce a value slightly different from the representation used for 0.3.
Interview Q: What is the difference between integer and floating-point division by zero?
Answer: Integer division by zero throws ArithmeticException. Floating-point division can yield positive infinity, negative infinity, or NaN, depending on the operands.
Interview Q: How should code test whether a double is NaN?
Answer: Use Double.isNaN(value). The equality comparison value == Double.NaN is always false, including when value itself is NaN.
7. A compact prediction process for interview code
When you see a numeric expression, use this order:
- Identify each operand’s declared or literal type. Remember that integer literals are usually
int, while decimal literals are usuallydouble. - Determine numeric promotion. For arithmetic, apply the
double,float,long,intpriority order. - Evaluate using that promoted type. Check for integral overflow, integer division, or floating-point precision behavior.
- Apply assignment conversion last. Ask whether assignment widens automatically, requires an explicit cast, or includes a special rule such as compound assignment.
- Check exceptional cases. Integral division by zero throws; floating-point division uses infinity or NaN; integer overflow itself does not throw.
For example:
byte a = 100;
byte b = 30;
int result = a + b;
byte narrowed = (byte) (a + b);
System.out.println(result); // 130
System.out.println(narrowed); // -126
The arithmetic is performed as int, giving 130. The cast to byte then retains only the low-order 8 bits, producing -126.
Key takeaways
- Widening conversions are implicit, but widening to
floatordoublemay lose exact precision. - Narrowing conversions generally require a cast and can discard bits, truncate fractions, or clamp out-of-range floating-point values.
- Arithmetic on
byte,short, andcharnormally promotes operands toint. - The type of an expression is determined before assignment. A
longdestination does not undo earlierintoverflow. - Integral overflow wraps silently; use
Math.*Exactmethods when overflow must be detected. - Floating-point arithmetic can produce rounding error, infinities, signed zero, and NaN.
NaNis not equal to itself; test it withDouble.isNaNorFloat.isNaN.
Next, you will examine autoboxing and unboxing: how Java converts between primitives and wrapper objects, why Integer identity can be surprising, and how null unboxing can cause a NullPointerException.