Command Palette

Search for a command to run...

PHASE 1Beginner ~28 min· topic 6 of 14

Topic 1.6

Type Conversion and Casting

In one line

Java converts a value to a bigger type automatically (widening) because nothing can be lost, but makes you write a cast like (int) to convert to a smaller type (narrowing), because digits or bits may be thrown away. Knowing exactly what a cast keeps and drops, and when Java promotes types inside expressions, explains most 'lossy conversion' errors and many silent bugs.

Think of it like this

Pouring water. From a small glass into a big jug, it always fits, so nobody needs to ask. From a full jug into a small glass, some spills, so you must say 'yes, I know, pour anyway'. Widening in Java is the glass into the jug: automatic. Narrowing is the jug into the glass: you must write a cast to say you accept the spill.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Conversion
Changing a value from one type to another, such as from int to double.
Widening
Converting to a type that can hold every value of the original, like int to long. Java does it automatically.
Narrowing
Converting to a smaller type that might not hold the value, like double to int. Java requires a cast.
Cast
The type name in brackets before a value, like (int) 9.99. It tells the compiler you accept any loss.
Truncate
Cut off the decimal part without rounding. 9.99 truncates to 9.
Numeric promotion
Java's rule that converts small types to int (or to the bigger operand's type) before arithmetic.
Lossy
A conversion that might lose information: digits, bits or precision.
Parse
Read text and turn it into a value, like turning the String "42" into the int 42.

Step by step

01The widening ladder

Each step up the ladder can hold every value of the step below, so Java climbs it for you without being asked. long total = 5; widens the int 5. double avg = total; widens again.

char joins at int: it's unsigned, so it can't widen to short (which is signed and can't hold 40,000) but fits in int. byte and short can't widen to char either, because they can be negative.

The widening ladderdiagram
Rendering diagram…

02Narrowing needs a cast, and here is what it drops

Decimal → integer: the fraction is dropped, rounding toward zero. (int) 7.9 is 7, (int) -7.9 is -7. If the value is beyond the int range it is clamped: (int) 1e20 is 2,147,483,647. (int) Double.NaN is 0.

Integer → smaller integer: only the low bits are kept. (byte) 200: 200 is 11001000, and as a byte the top bit is the sign, so it reads as -56. (byte) 256 is 0, because 256's low 8 bits are all zero.

Decimal → byte/short/char happens in two steps: first to int (truncate and clamp), then to the small type (keep low bits). So (byte) 300.7 is (byte) 300, which is 44.

Narrowing needs a cast, and here is what it dropsdiagram
Rendering diagram…

03Promotion inside expressions

Java's bytecode only has arithmetic for int, long, float and double. So whenever you compute with byte, short or char, they're first widened to int, and the result is an int.

Mixed types climb to the bigger one: int + long is long, long + float is float, int + double is double. This is why 5 / 2 is 2 (both int) while 5 / 2.0 is 2.5 (the int is promoted to double).

The type depends on the operands, not on where you store the result. double avg = sum / count; with two ints does an integer division first and only then widens the truncated answer.

04Casting at the right moment

A cast applies to the value immediately after it. (double) total / count casts total first, so the division is done in double. (double) (total / count) divides in int first (truncating) and casts the already-truncated result.

The same goes for big products: (long) a * b widens a before multiplying, while (long) (a * b) multiplies in int (possibly overflowing, Topic 1.3) and widens too late.

05Compound assignment hides a cast

The JLS defines x op= y as x = (T) (x op y) where T is the type of x. That built-in cast is why byte b = 10; b += 5; compiles, and why char c = 'a'; c += 1; gives 'b'.

It's convenient but can hide loss: int n = 10; n += 3.7; computes 13.7 in double, then casts to int: 13, with no warning.

06Converting between text and numbers

A cast can't turn text into a number: (int) "42" doesn't compile. Use the parse methods: Integer.parseInt("42"), Long.parseLong, Double.parseDouble, Boolean.parseBoolean. They throw NumberFormatException if the text isn't a valid number.

Integer.parseInt("ff", 16) reads other bases. In the other direction, String.valueOf(42), Integer.toString(42) or "" + 42 all give "42", and Integer.toString(255, 2) gives binary.

07Rounding is not casting

Math.round(x) rounds to the nearest whole number, with halves going up (toward positive infinity): Math.round(2.5) is 3 and Math.round(-2.5) is -2. It returns a long for a double argument, so int r = Math.round(2.5); needs a cast.

Math.floor rounds down and Math.ceil rounds up, both returning double. For money rounding with a chosen rule, use BigDecimal.setScale (Topic 1.4).

Try it yourself

  1. 1

    Predict the bytes

    Before running, predict (byte) 127, (byte) 128, (byte) 255 and (byte) -129. Then add them to the second example. (Answers: 127, -128, -1, 127.)

  2. 2

    Move the cast

    In the third example, change (double) total / count to total / (double) count and then to total / count * 1.0. The first gives 3.5; the second gives 3.0, because total / count is evaluated first in int.

  3. 3

    Parse something that isn't a number

    In the last example, change "42" to " 42" (with a space) and run. parseInt doesn't trim spaces, so you get a NumberFormatException. Fix it with " 42".strip() (Java 11) or trim().

Code & diagrams

Widening is automatic, narrowing truncates New tab
Sign in to run this example in your browser.

Expected output

int -> long -> double: 100.0
(int) 9.99        = 9
(int) -9.99       = -9
Math.round(9.5)   = 10
Math.round(-9.5)  = -9
Math.floor(-9.99) = -10.0
Math.ceil(9.01)   = 10.0
What narrowing really keeps New tab
Sign in to run this example in your browser.

Expected output

(byte) 200         = -56
(byte) 256         = 0
(short) 70000      = 4464
(int) 3000000000L  = -1294967296
(char) 66          = B
(int) 1e20         = 2147483647
(int) -1e20        = -2147483648
(int) NaN          = 0
(byte) 300.7       = 44
Promotion, compound assignment and silent precision loss New tab
Sign in to run this example in your browser.

Expected output

sum=30 c=30 a=15
7 / 2            = 3
(double) 7 / 2   = 3.5
(double) (7 / 2) = 3.0
10 += 3.7  -> 13
int to float: 16777217 -> 16777216
Text to numbers and back New tab
Sign in to run this example in your browser.

Expected output

43
7.5
9000000
43!
255
11111111

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Add to a byte without a cast

Write byte level = 10; level = level + 1;.

terminal
$ javac Main.java
── what you'll see ──
Main.java:4: error: incompatible types: possible lossy conversion from int to byte
level = level + 1;
^
1 error

Break #2

Store a double in an int

Write double price = 3.7; int rupees = price;.

terminal
$ javac Main.java
── what you'll see ──
Main.java:4: error: incompatible types: possible lossy conversion from double to int
int rupees = price;
^
1 error

Break #3

Parse text that isn't a number

Call Integer.parseInt("12a").

terminal
$ javac Main.java
java Main
── what you'll see ──
Exception in thread "main" java.lang.NumberFormatException: For input string: "12a"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
at java.base/java.lang.Integer.parseInt(Integer.java:778)
at Main.main(Main.java:3)

Myth vs fact

Myth

Casting a double to int rounds it.

Fact

It truncates toward zero: (int) 2.99 is 2 and (int) -2.99 is -2. Use Math.round to round.

Myth

Widening never loses anything.

Fact

Integer to integer widening is exact, but int → float, long → float and long → double can round away the last digits.

Myth

b += 1 and b = b + 1 are identical.

Fact

Compound assignment includes a cast to the variable's type, so b += 1 compiles for a byte while b = b + 1 doesn't.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Primitive conversions compile to single bytecodes: i2l, i2d, l2i, d2i, i2b, i2c, i2s and so on. d2i implements the JLS rules exactly: NaN → 0, out of range → MIN_VALUE/MAX_VALUE, otherwise truncate toward zero. There's no d2b; (byte) someDouble is d2i then i2b.

  • ▸

    Assignment of a constant expression of type int to byte, short or char is allowed without a cast if the value fits (JLS §5.2). That rule doesn't apply to method arguments: void f(byte b) called as f(5) fails to compile.

  • ▸

    Math.round(double) is defined as floor(x + 0.5) semantics without the double-rounding bug older JDKs had: since Java 7, Math.round(0.49999999999999994) correctly returns 0.

  • ▸

    Boxing conversions are separate from primitive ones: an int can box to Integer but not to Long, so Long x = 5; fails while long x = 5; works. Topic 8.1 and Phase 9 return to boxing.

Remember this

  1. 1

    Widening conversions go up the chain byte → short → int → long → float → double (and char → int). Java does them automatically wherever a bigger type is expected: in assignments, method arguments and arithmetic.

  2. 2

    Narrowing goes the other way and needs an explicit cast: int n = (int) 9.99;. Decimal to integer truncates toward zero (9.99 becomes 9, -9.99 becomes -9). Integer to a smaller integer keeps only the low bits, so (byte) 200 is -56. A double too big for an int saturates at Integer.MAX_VALUE, and NaN becomes 0.

  3. 3

    Numeric promotion: before +, -, *, /, % and comparisons, Java converts both operands to a common type: double if either is double, else float if either is float, else long if either is long, otherwise int. So byte + byte is an int, which is why byte b = b1 + b2; needs a cast.

  4. 4

    Compound assignment (+=, -=, *= …) includes a hidden cast back to the variable's type. b += 1 compiles where b = b + 1 doesn't, and int n = 10; n += 3.7; silently makes n 13.

  5. 5

    Some widenings still lose precision silently: int → float, long → float and long → double keep the magnitude but can round the last digits. float f = 16_777_217; stores 16,777,216.

  6. 6

    Casting doesn't round. Use Math.round (returns long for a double), Math.floor or Math.ceil when you want rounding. Text is a separate world: convert with Integer.parseInt("42"), Double.parseDouble, and back with String.valueOf(n). boolean can't be cast to or from anything.

Explain it without notes

01

What is the difference between widening and narrowing, and why does only one need a cast?

02

Why does byte c = a + b; fail when a and b are both byte?

03

Explain the result of (byte) 200 step by step.

04

Why does double avg = sum / count; give a whole number when sum and count are ints, and how do you fix it?

Practice

01

Compute the average of the ints 7, 8 and 10 as a double and print it rounded to the nearest whole number as an int.

02

Print what (byte), (short) and (char) casts do to the int 65601.

03

Convert the String "1250" to a number, add 10% using integer arithmetic only, and print the result as a String with " rupees" appended.

Trade-offs

  • ↔

    Explicit casts make every possible loss visible, at the cost of noisier code; Java chose safety over brevity, unlike C, which narrows silently.

  • ↔

    Compound assignment is concise and handles small types neatly, but its hidden cast can mask real precision loss (int += double).

  • ↔

    Using int everywhere avoids most casts but costs memory in huge arrays; byte/short save memory but need casts on every arithmetic store.

Done when you can

  • I can draw the widening ladder and say which conversions need a cast.

  • I can predict what a narrowing cast keeps for decimals and for integers.

  • I know the numeric promotion rules and why byte + byte is an int.

  • I place casts so division and multiplication happen in the right type.

  • I convert between Strings and numbers with parseInt/valueOf and know about NumberFormatException.

  • I use Math.round, floor or ceil when I mean rounding, not a cast.