Type Conversion and Casting

Know which numeric conversions Java does for you, what an explicit cast throws away, and how to convert strings safely.

Syntaxlong l = someInt; // widening, automatic int i = (int) someDouble; // narrowing, explicit, truncates int safe = Math.toIntExact(someLong); int n = Integer.parseInt(text.strip());

Java converts between numeric types in two directions, and treats them very differently.

Widening: automatic

Going to a bigger type happens without a cast: byte to short to int to long to float to double, and char to int and anything wider. The value is kept, with one exception: long or int to float, and long to double, can lose low digits because floats have fewer significant bits. (float) 16_777_217 becomes 1.6777216E7.

Narrowing: you must cast

Going to a smaller type needs an explicit cast, and the cast is your promise that the loss is acceptable. Java does not check anything:

  • Double to int truncates toward zero: (int) 9.99 is 9, (int) -9.99 is -9. Use Math.round when you mean rounding. Out-of-range values saturate instead of wrapping: (int) 1e10 is Integer.MAX_VALUE and (int) Double.NaN is 0.
  • A larger integer to a smaller one keeps the low bits: (byte) 200 is -56 and (int) 3_000_000_000L is negative. Use Math.toIntExact when the value must fit, so you get an exception instead of a wrong number.

Promotion inside expressions

Arithmetic on byte, short and char is done as int. If either side is long the whole operation is long, and if either side is double it is double. The usual bug is integer division: done / total * 100 with two ints is 0 for any done smaller than total, because 3 / 4 is 0 before the multiplication happens. Cast one operand before dividing, (double) done / total; casting the result, (double) (done / total), is too late.

Compound assignment hides a cast: short s = 1; s += 1; compiles, but s = s + 1; does not.

Strings are not numbers

You cannot cast a String to an int. Parse it with Integer.parseInt, Long.parseLong or Double.parseDouble, and go the other way with String.valueOf or Integer.toString. Parsing is strict: spaces, a decimal point in an integer, or a value out of range all throw NumberFormatException. Everything that arrives over HTTP or from environment variables is a string, so this is where back-end code converts: strip the input, parse it inside a try/catch, and report a clear 400 or a clear configuration error.

Reference casts, such as (String) obj, are a separate topic: they are checked at runtime and throw ClassCastException. Pattern matching with instanceof replaces most of them.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Compute a completion percentage for a job-status endpoint without integer division returning 0.
  • Convert a long row count from the database to an int page size with Math.toIntExact.
  • Parse PORT and timeout values from environment variables and fail with a clear message if they are not numbers.

More examples

Percentages and integer division

A cast binds tighter than division, so (double) done converts done before the divide. Wrapping the division in brackets casts its already-truncated int result.

Example · java
void main() {
    int done = 3, total = 4;
    IO.println(done / total * 100);             // 3 / 4 is 0
    IO.println(done * 100 / total);             // multiply first
    IO.println((double) done / total * 100);    // cast before dividing
    IO.println((double) (done / total) * 100);  // cast too late
}
// Output:
// 0
// 75
// 75.0
// 0.0

Checked narrowing and float precision

Casting a long that does not fit into an int keeps the low 32 bits and produces a negative number. Math.toIntExact throws instead, and the float example shows that even an automatic widening can drop precision.

Example · java
void main() {
    long rows = 3_000_000_000L;
    IO.println((int) rows);                  // silently wrong
    try {
        int n = Math.toIntExact(rows);
        IO.println(n);
    } catch (ArithmeticException e) {
        IO.println("toIntExact: " + e.getMessage());
    }
    IO.println((float) 16_777_217);          // widening that loses a digit
    IO.println((int) -9.99 + " " + Math.round(-9.99));
}
// Output:
// -1294967296
// toIntExact: integer overflow
// 1.6777216E7
// -9 -10

Parsing request input safely

Stripping first lets " 12 " parse, a missing value falls back to a default, and both bad formats and bad ranges become one exception type a handler can map to a 400.

Example · java
int parsePage(String raw) {
    if (raw == null || raw.isBlank()) return 1;
    try {
        int page = Integer.parseInt(raw.strip());
        if (page < 1) throw new IllegalArgumentException("page must be at least 1");
        return page;
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("page is not a number: " + raw.strip());
    }
}

void main() {
    for (String raw : new String[] {"3", " 12 ", null, "2.5", "0"}) {
        try {
            IO.println("page=" + parsePage(raw));
        } catch (IllegalArgumentException e) {
            IO.println("400: " + e.getMessage());
        }
    }
    IO.println(String.valueOf(42) + Integer.toString(7) + Long.toString(9L));
}
// Output:
// page=3
// page=12
// page=1
// 400: page is not a number: 2.5
// 400: page must be at least 1
// 4279

Discussion

  • Be the first to comment on this lesson.