Type casting: widening and narrowing

Sometimes a value has to move from one type of box to another: an int into a double, a double into an int. Moving to a bigger box is automatic and safe. Moving to a smaller box needs your permission, a cast, and can silently lose data. This lesson shows exactly what gets lost, bit by bit.

Module 2 · Language Basics⏱ 17 minBeginnerNo install needed8 practice problems
1 The story

The average that lost its decimals

Riya's five subject marks add up to 437. She writes double avg = total / 5; expecting 87.4. The program prints 87.0. She changed the variable to a double, so where did the .4 go?

42int automatic 42.0double Widening: nothing lost 87.4double (int) 87int .4 ✗ Narrowing: needs a cast, can lose data myphatshala.in
Riya

Sir, avg is a double, so why did I get 87.0 and not 87.4?

Arjun Sir

Because Java calculated total / 5 first, and both of those are ints. int divided by int gives an int: 87. Only then was 87 widened into the double box as 87.0. The .4 was lost before the double ever saw it.

Riya

So I need to make it a double before dividing?

Arjun Sir

Exactly: (double) total / 5. That's a cast. Today you'll learn which conversions Java does for you, which ones need a cast, and what you can lose.

2 Definition

What is type casting?

Formal definition (write this in exams)

Type casting (type conversion) is converting a value from one data type to another. A widening (implicit) conversion goes from a smaller to a larger type and is done automatically by the compiler, e.g. int to double. A narrowing (explicit) conversion goes from a larger to a smaller type and requires a cast operator (type), e.g. (int) 87.4; it may lose information through truncation or overflow.

In simple words

Small box → big box: Java does it for you, nothing spills. Big box → small box: you must write the cast to say “I know, do it anyway”, and anything that doesn't fit is cut off.

Words you'll meet in this lesson

WideningConverting to a type that can hold every value of the original. Automatic.
NarrowingConverting to a type with a smaller range or no decimals. Needs a cast.
Cast operator(type) written before a value: (int) price.
TruncationCutting off the decimal part: (int) 9.99 is 9, not 10.
Numeric promotionIn arithmetic, byte, short and char are first converted to int.
ParsingConverting text to a number, e.g. Integer.parseInt("42"). Not a cast.

The widening ladder

Automatic directionbyte → short → int → long → float → double, and char → int
Needs a castAny step down the ladder, e.g. double → int, int → byte, int → char
Never allowedboolean ↔ any number. (int) true doesn't compile.
double → intDecimal part is dropped (towards zero): (int) 9.99 = 9, (int) -9.99 = -9
int → byteOnly the lowest 8 bits are kept: (byte) 300 = 44
Expression typeThe result has the widest type involved, at least int: int / int = int, int / double = double

Exam tip: “widening = implicit = safe”, “narrowing = explicit = may lose data”. One trap: long → float is widening and automatic, but can still lose precision, because float keeps only about 7 significant digits.

3 Types and ways to use it

Kinds of conversions

Widening (implicit)

int n = 42; double d = n;
Automatic, no data lost (except the long→float precision case).

Safe

Narrowing (explicit)

double d = 87.4; int n = (int) d;
You must write the cast. Decimals are cut; big values wrap.

May lose data

Promotion in expressions

byte a = 10, b = 20;
a + b is an int. Mixing int and double gives a double.

Automatic

Compound assignment

byte b = 10; b += 300;
Compiles: += includes a hidden narrowing cast. Result: 54.

Hidden cast

Casting vs converting between text and numbers

GoalUseExample
double → int (cut decimals)cast(int) 87.4 → 87
double → nearest whole numberMath.roundMath.round(87.6) → 88 (a long)
char → its codecast or widening(int) 'A' → 65
Text → numberparseInteger.parseInt("42"), Double.parseDouble("19.5")
Number → textString.valueOf or + ""String.valueOf(42) → "42"

You can't cast between String and numbers: (int) "42" is a compile error. Strings are objects, not numbers. Use Integer.parseInt, which throws NumberFormatException if the text isn't a valid number.

4 Step by step

How Java decides the type of an expression

  1. Look at each operand's type

    In total / 5, total is int and 5 is an int literal.

    javac works this out at compile time and chooses the bytecode instruction: idiv for ints, ddiv for doubles.
  2. Promote small types to int

    byte, short and char become int before any arithmetic.

    The JVM has no byte-addition instruction at all. Small values are loaded as ints, which is why byte + byte is an int.
  3. Widen to the biggest type present

    If either side is long, float or double, the other side is widened to match.

    For (double) total / 5, the cast happens first (it binds tighter than /), so 437.0 / 5 uses ddiv and gives 87.4.
  4. Do the operation in that type

    int ÷ int = int (fraction dropped). double ÷ int = double.

    The result's type is fixed. Assigning an int result to a double variable widens it afterwards; the lost fraction can't come back.
  5. Assign, widening automatically or casting explicitly

    Storing into a bigger type is automatic. Storing into a smaller one needs a cast, or javac reports “possible lossy conversion”.

    A narrowing cast compiles to instructions like d2i (double to int) or i2b (int to byte), which drop bits or decimals.
5 See it happen

What a cast does to the bits: both views

Left: the code and what prints. Right: the value inside memory before and after.

Pouring values between boxes 6 steps
What you see int n = 42;double d = n; // automatic double avg = 87.4;int whole = (int) avg; int big = 300;byte b = (byte) big; int total = 437;double avg = total / 5; double avg = (double) total / 5; byte a = 10, b = 20;byte c = a + b; 42.0 87 44 ← not 300! 87.0 ← .4 lost 87.4 error: incompatible types: possible lossy conversion from int to byte Small → big: no cast needed. The decimal part is simply cut off. 300 doesn't fit in a byte (max 127). The division happened in int, before widening. Cast first, then divide: correct. byte + byte is actually an int. myphatshala.in
Behind the screen 42 (32 bits) i2d 42.0 (64 bits) Every int fits exactly in a double, so javac inserts the conversion itself. 87.4 d2i 87 d2i drops everything after the point, towards zero: 9.99 → 9, −9.99 → −9. For rounding, use Math.round instead. 300 as int (lowest 16 of 32 bits shown) 0000 0001 0010 1100 i2b: keep lowest 8 bits 0010 1100 32 + 8 + 4 = 44 (300 − 256) The 256 bit (in red) is thrown away. 437 int ÷ 5 int idiv 87 int i2d 87.0 The fraction was lost inside idiv. Widening afterwards can't bring it back. 437 int i2d 437.0 ÷ 5.0 ddiv 87.4 The cast applies to total only. Then 5 is widened to match, and ddiv keeps the fraction. byte 10 byte 20 → int iadd 30 (int) The JVM has no byte addition. Small types are promoted to int, so the result is an int. Fix: byte c = (byte) (a + b); myphatshala.in
6 Real-world use

Where conversions bite in real systems

Conversions are everywhere data crosses a boundary: from the database, the network, a file or a calculation.

Watch it with an example: a product's average rating

Three customers rate a phone cover 5, 4 and 5 stars. Watch how integer division loses the fraction, and how one cast fixes it.

int ÷ int vs (double) cast 5 steps
Animated example int sum = 5 + 4 + 5; int count = 3; double avg = sum / count; double avg = (double) sum / count; double avg = (double) sum / count; Math.round(avg * 10) / 10.0 sum14 count3 int ÷ int 14 / 3 = 4 .666 is thrown away cast → 14.0 / 3 double ÷ int → double 4.666666666666667 avg = 4.0 ✗ wrong rating avg = 4.666666666666667 ★ 4.7 Cast before dividing, round only for display. myphatshala.in
4.3 ★

1. Average ratings and percentages

Ratings, attendance percentages and averages all divide one count by another. Using doubles (or casting first) avoids showing 4.0 when the real average is 4.3.

sum of stars, count→(double) sum / count→4.3 ★
₹199.99 → ₹200

2. Rounding money for display or payment

Payment gateways often need amounts in paise as whole numbers. Converting with a plain cast truncates; systems use Math.round or BigDecimal with an explicit rounding mode.

₹199.995→round, don't truncate→20000 paise
"42" → 42"19.5" → 19.5"abc" → ✗

3. Reading numbers from text

Form inputs, CSV files and query parameters arrive as text. They're parsed, not cast, and invalid text must be handled (Module 9).

"42" from a form→Integer.parseInt→int 42
& 0xFF

4. Binary data from devices and files

Bytes read from a sensor, image or network packet are Java bytes (−128 to 127). To get the unsigned value 0–255, code converts with b & 0xFF.

byte -56 from sensor→b & 0xFF→200
long → int ?

5. Sizes and counts from big systems

File sizes, row counts and timestamps are usually long. Casting them to int to pass into an older API works until the value passes 2.1 billion, then silently goes wrong. Math.toIntExact throws an exception instead.

3 GB file→Math.toIntExact(size)→Error instead of a wrong number

Examples are simplified to show where conversions happen. Real systems vary.

7 Run the code

Riya's marks, converted correctly

public class Main {
    public static void main(String[] args) {
        int total = 437;
        double wrongAvg = total / 5;            // int division first
        double avg = (double) total / 5;        // cast first
        int whole = (int) avg;                  // truncates
        long rounded = Math.round(avg);         // rounds
        byte squeezed = (byte) 300;             // keeps lowest 8 bits
        int parsed = Integer.parseInt("42");    // text to number

        System.out.println(wrongAvg + " vs " + avg);
        System.out.println(whole + " " + rounded + " " + squeezed + " " + parsed);
    }
}
87.0 vs 87.4 87 87 44 42
  • total / 5int ÷ int = int (87). Widened to 87.0 only afterwards.
  • (double) total / 5The cast applies to total first, so the division is done in double.
  • (int) avgNarrowing: drops .4. It never rounds up.
  • Math.round(avg)Rounds to the nearest whole number and returns a long.
  • (byte) 300Only the lowest 8 bits survive: 44.
  • Integer.parseInt("42")Text to number is parsing, not casting.
8 Line by line

Every conversion, step by step

Watch each value change type. The note shows the bytecode conversion instruction where one happens.

Worked examples: real problems, solved and traced in memory

Casting changes a value from one type to another. Widening (int → double) is automatic and safe; narrowing (double → int, long → int) needs an explicit cast and can lose data. These programs show both, with real business numbers.

Example 1Reviews · ratings

Average rating: integer division vs casting

Problem statement

A restaurant has 3 reviews with total stars 13. Compute the average rating. Dividing two ints gives 4, not 4.33. Cast to double first to keep the fraction, then show the rating as whole stars by casting back to int.

Input
total stars 13, reviews 3
Expected output
int division: 4
average: 4.333333333333333
whole stars shown: 4

Approach

  1. 13 / 3 is integer division: 4.
  2. (double) 13 / 3 widens first: 4.333….
  3. (int) avg narrows: the fraction is cut off (not rounded).

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Payments · conversion

Squeezing a long into an int loses data

Problem statement

An old reporting system stores amounts in int. A large transfer of Rs 3,000,000,000 (in a long) is cast to int. Show the corrupted value and how to detect the problem with Math.toIntExact.

Input
amount 3000000000
Expected output
long: 3000000000, cast to int: -1294967296
refused: integer overflow

Approach

  1. Cast (int) amount: the extra bits are dropped silently.
  2. Math.toIntExact(amount) throws instead of losing data.
  3. Catch it and report the problem.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Ticketing · seats

From seat letter to row number and back

Problem statement

A bus booking app labels rows 'A' to 'L' but stores them as numbers 1–12. Convert 'H' to its row number and convert row 3 back to a letter, using char ↔ int casts.

Input
seat row 'H'; row number 3
Expected output
Row H is number 8
Number 3 is row C

Approach

  1. row - 'A' + 1: char arithmetic gives an int.
  2. (char) ('A' + n - 1) turns a number back into a letter.
  3. Print both conversions.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 2.4 in one screen

  • Widening (automatic): byte → short → int → long → float → double, char → int.
  • Narrowing (needs (type)): may truncate decimals or keep only the low bits.
  • (int) 9.99 = 9, (int) -9.99 = -9. Use Math.round to round.
  • Arithmetic promotes byte/short/char to int; the result type is the widest involved.
  • int / int = int. Cast before dividing: (double) a / b.
  • +=, -=… include a hidden cast.
  • Text ↔ number is parsing/formatting, not casting. boolean never converts.

Interview and viva questions

What is the difference between implicit and explicit casting?

Implicit (widening) casting is done automatically by the compiler when converting to a type that can hold all values, like int to long. Explicit (narrowing) casting must be written with a cast operator when converting to a smaller or less precise type, like double to int, and may lose data.

Why does byte b = 10; compile if 10 is an int?

Because 10 is a compile-time constant that fits in a byte, Java allows the implicit narrowing for constants. byte b = x; where x is an int variable would not compile.

Why is long → float called widening if it can lose precision?

Because float's range is larger than long's (up to about 3.4 × 1038), so no value overflows. But float has only about 7 significant digits, so large longs are rounded. Java classifies it as widening but warns in the specification about precision loss.

What happens when you cast a very large double to int?

For double to int, Java uses saturation: values above Integer.MAX_VALUE become MAX_VALUE, values below MIN_VALUE become MIN_VALUE, and NaN becomes 0. For int to byte, the bits are chopped instead, so the result can wrap around.

Does s += 1 work when s is a short?

Yes. Compound assignment operators include an implicit cast back to the variable's type, so s += 1 compiles while s = s + 1 doesn't.

Practice problems

Widen automatically, narrow with care. Predict each output before running. The last three are business tasks.

P1Easy

Automatic widening

Assign an int 50 to a double and print it.

Input
50
Expected output
50.0
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        int i = 50;
        double d = i;
        System.out.println(d);
    }
}
50.0

How it works: int fits into double without loss, so no cast is needed; it prints 50.0.

P2Easy

Truncation

Cast 9.99 to int and print it.

Input
9.99
Expected output
9
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        System.out.println((int) 9.99);
    }
}
9

How it works: casting to int drops the fraction; it does not round.

P3Easy

char to int

Print the int value of '0' and of '9'.

Input
'0', '9'
Expected output
48 57
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        System.out.println((int) '0' + " " + (int) '9');
    }
}
48 57

How it works: digit characters have codes 48 to 57, not 0 to 9.

P4Medium

Digit character to number

Convert the character '7' to the number 7 and add 1.

Input
'7'
Expected output
8
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        char c = '7';
        int digit = c - '0';
        System.out.println(digit + 1);
    }
}
8

How it works: subtracting '0' (48) turns a digit character into its value.

P5Medium

Rounding vs casting

For 7.6, print the cast to int and Math.round.

Input
7.6
Expected output
7 8
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        double x = 7.6;
        System.out.println((int) x + " " + Math.round(x));
    }
}
7 8

How it works: casting cuts to 7; rounding gives 8.

P6Business problem

Percentage of target

Sales were 37 out of a target of 40. Print the percentage with decimals.

Input
37, 40
Expected output
Achieved 92.5%
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        int sold = 37, target = 40;
        double pct = (double) sold / target * 100;
        System.out.println("Achieved " + pct + "%");
    }
}
Achieved 92.5%

How it works: without the cast, 37 / 40 would be 0 and the answer 0.0%.

P7Business problem

Whole cartons needed

A shop must ship 250 bottles, 24 per carton. Print cartons needed (round up).

Input
250, 24
Expected output
Cartons: 11
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        int cartons = (int) Math.ceil(250 / 24.0);
        System.out.println("Cartons: " + cartons);
    }
}
Cartons: 11

How it works: Math.ceil rounds up (10.41 → 11.0); the cast turns the double into an int.

P8Business problem

Price to paise

Convert Rs 129.95 to paise as a long, once with a plain cast and once with rounding.

Input
129.95
Expected output
cast: 12994 paise, rounded: 12995 paise
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        double rupees = 129.95;
        long cast = (long) (rupees * 100);
        long rounded = Math.round(rupees * 100);
        System.out.println("cast: " + cast + " paise, rounded: " + rounded + " paise");
    }
}
cast: 12994 paise, rounded: 12995 paise

How it works: 129.95 × 100 is 12994.999999999998 in binary floating point, so the cast cuts it to 12994, one paisa short. Rounding gives the correct 12995. Always round when converting money.

Real-world scenario questions (asked in company interviews)

Conversion bugs don't crash; they quietly produce wrong numbers. Decide your answer first, then open the model answer.

Food delivery · ratingsFresher

Every restaurant shows a whole-number rating

A restaurant listing page shows ratings like 4.0 and 3.0 but never 4.3 or 3.7. The code is double rating = totalStars / reviewCount;, where both are ints read from the database. What's wrong and how would you fix it?

The interviewer is checking: do you spot integer division and know where the cast must go?

Show a strong answer

Both operands are ints, so Java does integer division and drops the fraction before the result is widened into the double.

  • Fix: double rating = (double) totalStars / reviewCount;, casting before dividing.
  • Guard against reviewCount == 0 (no reviews yet), which would otherwise give an error or NaN.
  • Round only for display, e.g. to one decimal place, and add a unit test with an average like 4.33.

Avoid: (double) (totalStars / reviewCount). The cast is too late; the division has already dropped the fraction.

Payments · checkoutFresher

Customers are charged one paisa less

Before calling the payment gateway, the code converts a rupee amount to paise with long paise = (long) (amount * 100);. For ₹19.99, the gateway receives 1998 paise. Why?

The interviewer is checking: do you understand that floating-point results can be slightly below the expected value, and that casts truncate?

Show a strong answer

19.99 can't be stored exactly as a double. 19.99 * 100 comes out as something like 1998.9999999999998, and the cast truncates it to 1998.

  • Short-term: use Math.round(amount * 100) instead of a cast.
  • Proper fix: never hold money in double. Keep amounts in paise (long) or BigDecimal from the moment they enter the system.
  • Add tests with known problem values such as 19.99, 0.29 and 1.15.

Avoid saying: “The gateway has a bug.” The truncation happened in our code.

Cloud storage · uploads1–3 years

Large uploads show a negative size

Users uploading files larger than about 2 GB see a negative file size, and the progress bar breaks. The upload code reads long size = file.length(); and passes (int) size to an older progress-tracking method. Explain and fix.

The interviewer is checking: do you know what a narrowing cast does to values beyond the target range?

Show a strong answer

An int holds at most 2,147,483,647 bytes (about 2 GB). Casting a larger long to int keeps only the low 32 bits, which can be read as a negative number. The cast hid the problem from the compiler.

  • Change the progress method to accept a long.
  • If an int is truly required, use Math.toIntExact(size), which throws an exception instead of producing a wrong value, and handle that case.
  • Search the codebase for other (int) casts on sizes, counts and timestamps.

Avoid: adding casts just to make the compiler quiet. A cast says “I accept data loss”.

IoT · sensor data1–3 years

Negative humidity readings

A device sends humidity as one byte from 0 to 255. Readings above 127 show up in the Java dashboard as negative numbers, like −56 for a real value of 200. What's happening?

The interviewer is checking: do you know that Java's byte is signed, and how to read unsigned values?

Show a strong answer

Java's byte is signed: −128 to 127. The bit pattern for 200 (1100 1000) has the top bit set, so Java reads it as −56.

  • Convert to an unsigned int: int humidity = b & 0xFF; (or Byte.toUnsignedInt(b)). This keeps the 8 bits and treats them as 0–255.
  • Document the byte format of the device protocol, and add tests with values above 127.

Avoid: adding 256 to negative values in scattered places. Convert once, where the bytes are read.