Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 2 – Variables, types and operators

char, boolean, casting and conversions

Widening and narrowing casts in Java, char arithmetic such as 'a' + 1, why (byte) 200 is -56 and (int) 3.99 is 3, and the hidden cast inside +=.

  • Beginner
  • 15 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Compute with char values and convert between char and int
  • Explain why a boolean never converts to or from a number, and write the conversion you mean
  • Predict which widening conversions keep a value and which can round it
  • Predict truncation and sign changes when a narrowing cast drops bits
  • Explain the cast hidden in compound assignment and the numeric promotion behind it

Before you start

What you’ll learn · Before you start

What you will learn

  • Compute with char values and convert between char and int
  • Explain why a boolean never converts to or from a number, and write the conversion you mean
  • Predict which widening conversions keep a value and which can round it
  • Predict truncation and sign changes when a narrowing cast drops bits
  • Explain the cast hidden in compound assignment and the numeric promotion behind it

Before you start

On this page

Java converts between numeric types in two directions. A widening conversion: A conversion to a type that holds every value of the old one, such as int to long or char to int, which Java applies on its own. Between integer types it never changes the value; int or long to float and long to double can round., such as int to long or char to int, goes to a type whose range covers every value of the old one, so Java applies it on its own. A narrowing conversion: A conversion to a type that may not hold the value, such as int to byte or double to int. It needs a cast: integers keep their lowest bits, and floating-point values are cut toward zero and clamped to the range. goes the other way and needs a cast: An explicit conversion written as (type) value, such as (byte) 200 or (int) 3.99. A cast between primitive types never throws, even when the value changes: (byte) 200 is -56., such as (byte) 200 or (int) 3.99, because the value may change: integers keep only their lowest bits, so (byte) 200 is -56, and floating-point values lose their fraction, so (int) 3.99 is 3.

Two types need their own rules. A char is a number, the 16-bit UTF-16 code of a character, so 'a' + 1 is the int 98. A boolean is not a number at all, and no cast turns it into one. You will run every kind of conversion on JDK 25, step through the bits a cast keeps and the cast hidden inside +=, and finish by shifting letters with char arithmetic.

A char is a number

A char holds one UTF-16 code unit: One 16-bit number of the UTF-16 encoding of text, the unit a Java char holds and String.length() counts. Most characters are one code unit; characters beyond the first 65,536, such as many emoji, take two., an unsigned number from 0 to 65,535. Most characters you type, Latin and Devanagari letters included, are one code unit each, so for them a char is simply the character’s code:

Chars.java Java
// A char is a number: the UTF-16 code of one character, from 0 to 65535.
void main() {
    char letter = 'a';
    IO.println("'a' + 1          = " + ('a' + 1));            // char + int is an int
    IO.println("(char) ('a' + 1) = " + (char) ('a' + 1));
    letter++;                                                 // ++ keeps the char type
    IO.println("after letter++   = " + letter);
    IO.println("codes of A, Z, a, 0: " + (int) 'A' + " " + (int) 'Z' + " " + (int) 'a' + " " + (int) '0');
    IO.println("'7' - '0'        = " + ('7' - '0'));          // a digit character to its value
    IO.println("'a' + 'b'        = " + ('a' + 'b'));          // two chars add up as numbers
    IO.println("\"\" + 'a' + 'b'   = " + ("" + 'a' + 'b'));   // after a String, + joins text
    char ka = 'क';
    IO.println(ka + " is " + Character.getName(ka) + ", code " + (int) ka + ", a letter: " + Character.isLetter(ka));
    String smile = "😀";
    IO.println(smile + " is " + smile.length() + " chars but " + smile.codePointCount(0, smile.length()) + " code point");
}
Chars, booleans, and three lines the compiler refuses Python · chars_session.py
from session import run, version

version()
run("""
java Chars.java
java Booleans.java
!java BooleanErrors.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Chars.java
'a' + 1          = 98
(char) ('a' + 1) = b
after letter++   = b
codes of A, Z, a, 0: 65 90 97 48
'7' - '0'        = 7
'a' + 'b'        = 195
"" + 'a' + 'b'   = ab
क is DEVANAGARI LETTER KA, code 2325, a letter: true
😀 is 2 chars but 1 code point
$ java Booleans.java
empty: false, ready: true
as a number: 1
parseBoolean: true false false
$ java BooleanErrors.java
BooleanErrors.java:3: error: incompatible types: int cannot be converted to boolean
    if (items) {                  // an int is not a condition
        ^
BooleanErrors.java:6: error: incompatible types: int cannot be converted to boolean
    boolean ready = 1;            // 1 is not true
                    ^
BooleanErrors.java:7: error: incompatible types: boolean cannot be converted to int
    int flag = (int) ready;       // and no cast turns a boolean into a number
                     ^
3 errors
error: compilation failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 chars_session.py

  • 'a' + 1 is the int 98: arithmetic turns a char into an int first. Cast the result back, (char) ('a' + 1), to get the character b.
  • letter++ keeps the type: increment and decrement operators, like compound assignment below, convert the result back to the variable’s type.
  • '7' - '0' is 7, because the digit characters have consecutive codes, 48 to 57. Character.getNumericValue does the same for digits of other scripts too.
  • 'a' + 'b' adds two codes and prints 195. Once a String stands on the left, + joins text instead, so "" + 'a' + 'b' is "ab".
  • 'क' is a Devanagari letter with the code 2325, and Character.isLetter knows it. Since JDK 18 Java reads source files as UTF-8 by default, so such letters can stand in the code as they are.
  • The emoji 😀 is outside the first 65,536 code points, so it takes two char values, a surrogate pair: Two UTF-16 code units, a high one from D800 to DBFF and a low one from DC00 to DFFF, that together encode one character beyond U+FFFF, such as an emoji. length() counts both; codePointCount counts one.: length() counts code units, codePointCount counts characters. A later lesson on Unicode covers this properly.

The codes the run printed follow a pattern worth knowing by heart:

Character Its code What follows from it
A 65 A to Z are 65 to 90
a 97 each lower-case letter is 32 above its capital
0 48 '7' - '0' is the digit’s value, 7
क 2325 one code unit, like every Devanagari letter
😀 two code units length() counts 2 for it
Unicode Character Inspector Paste any text to see each character's code point and its UTF-16 code units.

Checkpoint

Check: A char is a number

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    IO.println('b' + 1);
    Show the answer: Question 1 of 2

    Answer: 99

    Arithmetic turns a char into its int code first: 'b' is 98, so 'b' + 1 is the int 99. (char) ('b' + 1) would print c.

  2. Question 2 of 2 What is '9' - '0'?

    Type a number.

    A hint

    The digit characters have consecutive codes, from 48 for 0.

    Show the answer: Question 2 of 2

    Answer: 9

    '0' is 48 and '9' is 57, and their difference is the digit's value, 9. That is the usual way to turn a digit character into a number.

A boolean is not a number

boolean has exactly two values, true and false, and Java has no conversion between it and any number. A condition must be a boolean, so the C habit of if (count) does not compile, and neither does boolean ready = 1 or a cast such as (int) ready. The last command of the session above shows all three errors, and each has a direct way to say what was meant:

Written as in C What the compiler says Write instead
if (items) int cannot be converted to boolean if (items != 0)
boolean ready = 1; int cannot be converted to boolean boolean ready = true;
int flag = (int) ready; boolean cannot be converted to int int flag = ready ? 1 : 0;

Booleans come from comparisons and logic: items == 0 and !empty && items < 10 are boolean expressions. When you need 1 or 0, say so with the conditional operator, ready ? 1 : 0. Boolean.parseBoolean returns true only for the text “true” in any mix of upper and lower case; “yes”, “1” and null all give false.

Checkpoint

Check: A boolean is not a number

  1. Question 1 of 2 items is an int. Which condition compiles?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: if (items != 0)

    A condition must be a boolean. Write the comparison you mean, such as items != 0 or items > 0.

  2. Question 2 of 2 a, b and c are booleans. Which expression counts how many of them are true?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: (a ? 1 : 0) + (b ? 1 : 0) + (c ? 1 : 0)

    Java has no conversion between boolean and any number. Say what you mean with ? : and add the results.

Widening happens on its own

Java applies a widening conversion wherever one is needed: in an assignment, when passing an argument, and inside an expression. The diagram shows every widening path: follow the arrows from a type to the types it widens to.

Widening paths: byte to short to int to long to float to double, and char to int; int or long to float and long to double may round.Widening conversions happen on their ownbyteshortintlongfloatdoublecharint to double: exactint to float: may roundlong to double: may roundkeeps the value exactlymay round to the nearest float or double

Widening conversions, and the three that can round the value

Text description of the diagram

Six boxes stand in a row, byte, short, int, long, float and double, with arrows from each to the next; a seventh box, char, has an arrow to int. Java applies these conversions on its own, and any path along the arrows is allowed: byte to long, char to double and so on.

  • Solid arrows keep the value exactly: byte to short, short to int, char to int, int to long, float to double, and the arc from int to double.
  • Dashed arrows may round the value to the nearest floating-point number: long to float, the arc from int to float, and the arc from long to double. A float has 24 significant bits and a double 53, so an int or long with more significant bits than that cannot always be held exactly.

Widening never throws, and between integer types it never changes the value. Three widenings can still round, the dashed arrows of the diagram, because a float has 24 significant bits and a double 53:

Widening.java Java
// Widening conversions happen on their own; three of them can round the value.
void main() {
    byte small = 100;
    int i = small;                // byte to int
    long l = i;                   // int to long
    double d = l;                 // long to double
    char c = 'A';
    int code = c;                 // char to int
    IO.println(i + " " + l + " " + d + " " + code);

    int big = 16_777_217;         // 2 to the power 24, plus 1
    float f = big;                // int to float compiles, but a float has 24 bits of precision
    IO.println(big + " as a float = " + f + ", back to int = " + (int) f);

    long huge = 9_007_199_254_740_993L;   // 2 to the power 53, plus 1
    double asDouble = huge;               // long to double: 53 bits of precision
    IO.println(huge + " as a double, back to long = " + (long) asDouble);
}
Widening, then narrowing casts Python · casts_session.py
from session import run, version

version()
run("""
java Widening.java
java Narrowing.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Widening.java
100 100 100.0 65
16777217 as a float = 1.6777216E7, back to int = 16777216
9007199254740993 as a double, back to long = 9007199254740992
$ java Narrowing.java
(byte) 200          = -56
(byte) 256          = 0
(byte) -129         = 127
(short) 70000       = 4464
(int) 3000000000L   = -1294967296
(char) 66           = B
(int) (char) -1     = 65535
(int) 3.99          = 3
(int) -3.99         = -3
(int) 1e10          = 2147483647
(int) Double.NaN    = 0
(byte) 300.7        = 44
200 in bits: 11001000, as a byte: -56
300 in bits: 100101100, as a byte: 44

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 casts_session.py

16,777,217 is 2²⁴ + 1, which needs 25 significant bits, so as a float it becomes the nearest float, 16,777,216, and the cast back to int shows the change. 9,007,199,254,740,993, which is 2⁵³ + 1, loses its last bit the same way as a double. Most code never meets numbers that large, but IDs, time stamps in nanoseconds and money in the smallest unit can, so keep them in long and never send them through float or double.

Key idea

Widening between integer types never changes a value; int or long to float, and long to double, compile without a cast and can still round a large value to the nearest one the type holds.

Checkpoint

Check: Widening conversions

  1. Question 1 of 2 int i = 1000; is declared. Which assignment compiles without a cast?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: long l = i;

    Widening goes to a type whose range covers every value of the old one: int to long, float or double, where only float can round a large int. Every other direction needs a cast.

  2. Question 2 of 2 Which of these widening conversions can change a value?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: int to float

    The dashed arrows of the diagram can round: int or long to float, and long to double. Widening.java printed 16777217 as a float = 1.6777216E7.

Narrowing needs a cast

A conversion that can lose information must be asked for with a cast, (type) value. What it keeps depends on the source type. The second program of the session above, Narrowing.java, shows each case:

Narrowing.java Java
// Narrowing casts: integers keep their low bits, floating-point values are cut toward zero first.
void main() {
    IO.println("(byte) 200          = " + (byte) 200);
    IO.println("(byte) 256          = " + (byte) 256);
    IO.println("(byte) -129         = " + (byte) -129);
    IO.println("(short) 70000       = " + (short) 70_000);
    IO.println("(int) 3000000000L   = " + (int) 3_000_000_000L);
    IO.println("(char) 66           = " + (char) 66);
    IO.println("(int) (char) -1     = " + (int) (char) -1);
    IO.println("(int) 3.99          = " + (int) 3.99);
    IO.println("(int) -3.99         = " + (int) -3.99);
    IO.println("(int) 1e10          = " + (int) 1e10);
    IO.println("(int) Double.NaN    = " + (int) Double.NaN);
    IO.println("(byte) 300.7        = " + (byte) 300.7);
    IO.println("200 in bits: " + Integer.toBinaryString(200) + ", as a byte: " + (byte) 200);
    IO.println("300 in bits: " + Integer.toBinaryString(300) + ", as a byte: " + (byte) 300);
}
  • Integer to a smaller integer: the low bits stay. 200 is 11001000 in binary. A byte keeps those 8 bits and reads them as a signed number, -56. 256 is 1 followed by eight zeros, so (byte) 256 is 0, and (short) 70000 keeps the low 16 bits, 4,464. The sign can flip either way: (byte) -129 is 127.
  • Integer to char: also the low bits, read as unsigned. (char) -1 is the char 65,535.
  • Floating point to an integer: cut toward zero, then clamp. (int) 3.99 is 3 and (int) -3.99 is -3. A value beyond the range becomes the nearest limit, so (int) 1e10 is 2,147,483,647, and NaN becomes 0.
  • Floating point to byte, short or char: two steps. The value is cut to an int first, then narrowed as an integer: 300.7 becomes 300, and 300 as a byte is 44.

Step through the bits behind two of those lines, using the binary forms the last two lines of the run print:

Interactive · step by step The 8 bits a cast to byte keeps Step through (byte) 200, an int that changes sign, then (byte) 300.7, a double that is cut to an int first.
The 8 bits a cast to byte keeps, step 6 of 6. Showing the int 200, keep the low 8 bits, read them signed, (byte) 200 is -56, the double 300.7, the int 300, keep the low 8 bits, (byte) 300.7 is 44. In focus: (byte) 200 is -56, (byte) 300.7 is 44.(int)the int 200… 0000 0000 1100 1000keep the low 8 bits1100 1000read them signed−128 + 64 + 8(byte) 200 is -56the sign has flippedthe double 300.7cut toward zero firstthe int 300… 0000 0001 0010 1100keep the low 8 bits0010 1100(byte) 300.7 is 4432 + 8 + 4

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. 200 in an int. 200 is an int, 32 bits wide. In binary it is 11001000 with 24 zeros in front, as the line 200 in bits of the run shows.
  2. Keep 8 bits. (byte) 200 keeps the low 8 bits, 1100 1000, and drops the other 24. Nothing is checked and nothing is thrown.
  3. Read them signed. A byte reads its highest bit as minus 128, so 1100 1000 is −128 + 64 + 8 = -56. The positive 200 has become a negative byte.
  4. A double goes to int first. A cast from double to byte takes two steps. First the double is cut toward zero to an int, so 300.7 becomes 300.
  5. Then 8 bits. 300 is 100101100 in binary, as the last line of the run prints. The low 8 bits are 0010 1100, which is 32 + 8 + 4 = 44.
  6. Check before you cut. Neither cast throws, and neither warns. When a value that does not fit is a bug, test the range first or use Math.toIntExact for a long.

None of these throws. When a value that does not fit is a bug, check first: Math.toIntExact throws for a long outside the int range, and Math.round rounds a double to the nearest long instead of cutting it.

Programmer Calculator (Bitwise) Type 200 or 70000 and read the low 8 or 16 bits that a cast keeps.

Checkpoint

Check: Narrowing casts

  1. Question 1 of 2 What is (byte) 129?

    Type a number.

    A hint

    Keep the low 8 bits, then read the highest one as −128.

    Show the answer: Question 1 of 2

    Answer: -127

    129 is 1000 0001 in binary. As a byte the top bit counts −128, so the value is −128 + 1 = -127, which is also 129 − 256.

  2. Question 2 of 2 What is (int) Double.NaN?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 0

    Floating point to int cuts toward zero and clamps to the range, and NaN becomes 0, as Narrowing.java printed.

The cast hidden in compound assignment

b += 300 is not the same as b = b + 300. A compound assignment: An assignment operator such as +=, -= or *=: b += x means b = (T) (b + x), where T is the type of b, so the result is cast back to the variable's type, and narrowed without a warning. such as +=, -= or *= casts its result back to the type of the variable, so it compiles for a byte even when the result does not fit:

Compound.java and CompoundErrors.java

Java (Compound.java) · Compound.java

// A compound assignment casts its result back to the type of the variable.
void main() {
    byte b = 10;
    b += 300;                     // b = (byte) (b + 300): 310 does not fit, 54 is kept
    IO.println("byte 10 += 300   -> " + b);
    short s = 1000;
    s *= 1000;                    // 1,000,000 as a short
    IO.println("short 1000 *= 1000 -> " + s);
    int total = 5;
    total += 2.7;                 // total = (int) (5 + 2.7)
    IO.println("int 5 += 2.7     -> " + total);
    char c = 'a';
    c += 2;
    IO.println("char 'a' += 2    -> " + c);
    final byte x = 10;
    byte y = x + 20;              // a constant expression that fits: allowed
    IO.println("final byte x = 10; byte y = x + 20 -> " + y);
}

Java (CompoundErrors.java) · CompoundErrors.java

void main() {
    byte b = 10;
    b = b + 300;                  // b + 300 is an int
    byte x = 10, y = 20;
    byte z = x + y;               // so is x + y, even though 30 would fit
    char c = 'a';
    c = c + 1;                    // and c + 1
}
Compound assignment narrows, plain assignment refuses, a lint warns Python · compound_session.py
from session import run, version

version()
run("""
java Compound.java
!java CompoundErrors.java
javac -Xlint:lossy-conversions Compound.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Compound.java
byte 10 += 300   -> 54
short 1000 *= 1000 -> 16960
int 5 += 2.7     -> 7
char 'a' += 2    -> c
final byte x = 10; byte y = x + 20 -> 30
$ java CompoundErrors.java
CompoundErrors.java:3: error: incompatible types: possible lossy conversion from int to byte
    b = b + 300;                  // b + 300 is an int
          ^
CompoundErrors.java:5: error: incompatible types: possible lossy conversion from int to byte
    byte z = x + y;               // so is x + y, even though 30 would fit
               ^
CompoundErrors.java:7: error: incompatible types: possible lossy conversion from int to char
    c = c + 1;                    // and c + 1
          ^
3 errors
error: compilation failed
[exit status 1]
$ javac -Xlint:lossy-conversions Compound.java
Compound.java:4: warning: [lossy-conversions] implicit cast from int to byte in compound assignment is possibly lossy
    b += 300;                     // b = (byte) (b + 300): 310 does not fit, 54 is kept
         ^
Compound.java:10: warning: [lossy-conversions] implicit cast from double to int in compound assignment is possibly lossy
    total += 2.7;                 // total = (int) (5 + 2.7)
             ^
2 warnings

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compound_session.py

10 + 300 is 310, and the hidden cast to byte keeps its low 8 bits, 54. short 1,000 times 1,000 becomes 16,960 the same way, and total += 2.7 on an int drops the fraction. By default the compiler accepts all of these without a warning, so be careful with += on narrow types. Step through both forms side by side:

Interactive · step by step What b += 300 does that b = b + 300 cannot Step down both columns, the compound assignment on the left and the plain one on the right, for a byte b holding 10.
What b += 300 does that b = b + 300 cannot, step 6 of 6. Showing b += 300, b = b + 300, b + 300, b + 300, (byte) 310, an int into a byte, b is 54, Refused. In focus: (byte) 310, an int into a byte.b += 300b is a byte holding 10b = b + 300the same sum, no castb + 300promoted to int: 310b + 300promoted to int: 310(byte) 310the hidden castan int into a bytepossible lossy conversionb is 54the low 8 bits of 310Refusedthe file does not compile

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The same sum, twice. b is a byte holding 10. b += 300 on the left and b = b + 300 on the right both start by adding 300 to b.
  2. Numeric promotion. Arithmetic promotes a byte to int before it adds, so b + 300 is the int 310 in both columns. 310 does not fit in a byte, which stops at 127.
  3. The plain assignment is refused. b = b + 300 would store an int in a byte, a narrowing conversion that needs a cast. The compiler refuses it: possible lossy conversion from int to byte.
  4. The hidden cast. A compound assignment is defined as b = (byte) (b + 300). The cast is part of the operator, so this line compiles, by default without a warning.
  5. 54, without a word. The cast keeps the low 8 bits of 310, which is 310 − 256 = 54. The run printed byte 10 += 300 -> 54.
  6. The rule to keep. Every +=, -= and *= on a byte, short or char hides a cast. Use a wider variable when the result can grow, and write the cast yourself when you mean it.

The errors in CompoundErrors.java come from numeric promotion: The rule that converts the operands of an arithmetic operator to a common type before it computes: byte, short and char become at least int, so the sum of two bytes is an int, and an int and a long give a long.: the operands of +, -, * and the other arithmetic operators are promoted to at least int, so b + 300, x + y on two bytes and c + 1 on a char are all int, and an int does not go into a byte or a char without a cast. A constant expression is the exception: final byte x = 10; makes x + 20 a constant, 30, which the compiler knows fits, so byte y = x + 20; compiles.

javac can be asked to warn. The last command of the session compiles Compound.java with -Xlint:lossy-conversions, which flags b += 300 and total += 2.7, whose right-hand side would need a cast to be assigned to the variable. It says nothing about s *= 1000 or c += 2, whose constants fit, although the first one wraps. The lint is worth turning on in a build, but only a variable wide enough for the result prevents the wrap.

Counting with += in a byte or a short

short count = 0; with count += 1; in a loop compiles and runs until count passes 32,767, then it wraps to -32,768 without a word, because every += hides a cast. Count in an int or a long.

Checkpoint

Check: Promotion and compound assignment

  1. Question 1 of 2 byte b = 100; is followed by b += 100;. What is b now?

    Type a number.

    A hint

    Work out the int sum, then keep its low 8 bits.

    Show the answer: Question 1 of 2

    Answer: -56

    b + 100 is the int 200, and the hidden cast keeps its low 8 bits, 1100 1000, which a byte reads as -56, the same value as (byte) 200.

  2. Question 2 of 2 byte x = 10, y = 20; are declared. What is the type of x + y?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: int

    The operands of +, -, * and the other arithmetic operators are promoted to at least int, so x + y is an int, even though 30 would fit in a byte.

Practise: a Caesar shift

Write shift(text, k), which moves every letter from a to z and A to Z k places along the alphabet, wrapping round from z to a, and leaves every other character as it is. k may be negative or very large:

Caesar.java Java
// Exercise: shift(text, k) moves every letter a to z and A to Z k places along the alphabet, wrapping round from z
// to a, and leaves every other character as it is; k may be negative or very large. Write it, then run this file:
// every check must say ok.
String shift(String text, int k) {
    return text;                  // replace this line
}

int failures = 0;

void check(String text, int k, String expected) {
    String got = shift(text, k);
    if (!got.equals(expected)) failures++;
    IO.println((got.equals(expected) ? "ok      " : "FAILED  ") + "shift(\"" + text + "\", " + k + ") = \"" + got + "\"");
}

void main() {
    check("abc", 1, "bcd");
    check("xyz", 3, "abc");
    check("Hello, World!", 13, "Uryyb, Jbeyq!");
    check("abc", -1, "zab");
    check("Java 25", 1_000_000, "Xojo 25");
    check("Java", -1_000_000, "Vmhm");
    check("Zebra", Integer.MAX_VALUE, "Wbyox");
    check("café, नमस्ते", 2, "eché, नमस्ते");
    IO.println(failures + " of 8 checks failed");
    if (failures > 0) System.exit(1);
}

As given, the method returns the text unchanged:

The exercise before you start Python · exercise_session.py
from session import run, version

version()
run("""
!java Caesar.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Caesar.java
FAILED  shift("abc", 1) = "abc"
FAILED  shift("xyz", 3) = "xyz"
FAILED  shift("Hello, World!", 13) = "Hello, World!"
FAILED  shift("abc", -1) = "abc"
FAILED  shift("Java 25", 1000000) = "Java 25"
FAILED  shift("Java", -1000000) = "Java"
FAILED  shift("Zebra", 2147483647) = "Zebra"
FAILED  shift("café, नमस्ते", 2) = "café, नमस्ते"
8 of 8 checks failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py

First hint: reduce k first with Math.floorMod(k, 26) from the integer arithmetic lesson, then for a lowercase letter compute (char) ('a' + (c - 'a' + step) % 26). Adding k before reducing it overflows for Integer.MAX_VALUE, which the seventh check catches.

Caesar Cipher Encoder & Decoder Shift a message by any amount and compare with what your shift method returns.

When the eight shifts all say ok, take the questions below on codes, booleans, widening, the bits a cast keeps and the cast inside +=. A question you get wrong returns at the end of the round, with its reasons:

Practice that adapts to you

Practise this lesson

15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 15 What is the type of 'a' + 1?

    Choose one answer.

    Show the answer: Question 1 of 15

    Answer: int

    Arithmetic promotes char to int, so 'a' + 1 is the int 98. Cast it back with (char) ('a' + 1) to get 'b'.

  2. Question 2 of 15 What does this print?

    Read the code, then choose one answer.

    char c = 'x';
    IO.println(c + 2);
    IO.println((char) (c + 2));
    Show the answer: Question 2 of 15

    Answer: 122, then z

    c + 2 promotes the char to an int, so the first line prints the number 122, the code of 'x' plus 2. Casting it back to char gives the character z.

  3. Question 3 of 15 What does "Java😀".length() return?

    Type a number.

    A hint

    length() counts UTF-16 code units, and the emoji is outside the first 65,536 code points.

    Show the answer: Question 3 of 15

    Answer: 6

    The four letters are one code unit each, and 😀 is a surrogate pair of two, so length() is 6, while codePointCount counts 5 characters.

  4. Question 4 of 15 What does Boolean.parseBoolean("yes") return?

    Choose one answer.

    Show the answer: Question 4 of 15

    Answer: false

    Boolean.parseBoolean("TRUE") is true, but "yes", "1" and null all give false, as Booleans.java printed.

  5. Question 5 of 15 You need the number of conditions that are true out of a, b and c, three booleans. Which works?

    Choose one answer.

    Show the answer: Question 5 of 15

    Answer: (a ? 1 : 0) + (b ? 1 : 0) + (c ? 1 : 0)

    Java has no conversion between boolean and any number, so a + b and (int) a do not compile. The conditional operator turns each boolean into 1 or 0 explicitly.

  6. Question 6 of 15 A library returns an int status, 0 for success and anything else for an error. How do you test for success in Java?

    Choose one answer.

    Show the answer: Question 6 of 15

    Answer: if (status == 0)

    In Java a number never stands for true or false. Compare it with the value that means what you want, here status == 0.

  7. Question 7 of 15 Which widening conversion can change the value?

    Choose one answer.

    Show the answer: Question 7 of 15

    Answer: long to double

    A double has 53 significant bits and a long 64, so a large long is rounded to the nearest double. Every int fits exactly in a double, and widening between integer types never changes the value.

  8. Question 8 of 15 What is (int) (float) 16_777_217?

    Type a number.

    Show the answer: Question 8 of 15

    Answer: 16777216

    16,777,217 is 2²⁴ + 1 and needs 25 significant bits, but a float has 24, so it becomes the float 16,777,216, and the cast back gives that value, as Widening.java printed.

  9. Question 9 of 15 A method takes a double parameter and is called with the long 9,007,199,254,740,993. What value does the method receive?

    Choose one answer.

    Show the answer: Question 9 of 15

    Answer: 9,007,199,254,740,992, the nearest double

    Widening happens in method calls as well. 2⁵³ + 1 needs 54 significant bits, so the double holds 9,007,199,254,740,992, the value Widening.java printed after the round trip.

  10. Question 10 of 15 What is (int) -3.99?

    Choose one answer.

    Show the answer: Question 10 of 15

    Answer: -3

    Floating point to an integer type cuts toward zero: (int) 3.99 is 3 and (int) -3.99 is -3. Use Math.round or Math.floor when you mean another rule.

  11. Question 11 of 15 What is (byte) 130?

    Type a number.

    Show the answer: Question 11 of 15

    Answer: -126

    130 is 1000 0010 in binary. A byte keeps those 8 bits and reads them as a signed number: 130 − 256 = -126.

  12. Question 12 of 15 Which of these are true in Java?

    Choose every answer that is right.

    Show the answer: Question 12 of 15

    Answer:

    • (int) Double.NaN is 0
    • (int) -2.9 is -2
    • (int) 5e9 is 2147483647

    A cast from a floating-point type to int cuts toward zero, so -2.9 gives -2 and 2.9 gives 2. A value too large for an int becomes Integer.MAX_VALUE, and NaN becomes 0. Casts never throw.

  13. Question 13 of 15 byte b = 10; is followed by b += 300;. What is b?

    Type a number.

    Show the answer: Question 13 of 15

    Answer: 54

    b + 300 is the int 310, and the hidden cast keeps its low 8 bits: 310 − 256 = 54, as Compound.java printed.

  14. Question 14 of 15 short s = 1; is declared. Which statement compiles?

    Choose one answer.

    Show the answer: Question 14 of 15

    Answer: s += 100000;

    Arithmetic on a short gives an int, so the plain assignments need a cast. A compound assignment includes that cast, so s += 100000 compiles, and keeps only the low 16 bits of 100,001, which is -31,071.

  15. Question 15 of 15 char c = 'a'; is declared. Which of these lines compile?

    Choose every answer that is right.

    Show the answer: Question 15 of 15

    Answer:

    • c += 1;
    • c++;
    • c = (char) (c + 1);

    Numeric promotion makes c + 1 an int. ++, -- and compound assignment cast back to char on their own; a plain assignment needs the cast written out.

Interview questions

Fresher · Why does byte b = 10; b = b + 1; fail to compile while b += 1; works?

In b + 1 both operands are promoted to int, so the sum is an int, and assigning an int to a byte is a narrowing conversion that needs a cast. A compound assignment is defined as b = (byte) (b + 1): it includes that cast, so it compiles, and it silently keeps only the low 8 bits when the result does not fit, so b += 300 turns 10 into 54. Write the cast yourself when narrowing is what you want, and use a wider variable when it is not.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A checksum adds the bytes of a file with int sum = 0; for (byte b : data) sum += b;. For some files it disagrees with the checksum another program computes for the same file. Find the bug, fix it, and say which files expose it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    Prices arrive from a JSON feed as doubles and are turned into cents with (int) (price * 100). Some totals come out one cent short. Explain why, say how many prices are affected, and design a conversion that is always right.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A char is an unsigned 16-bit number: 'a' + 1 is the int 98, and (char) ('a' + 1) is 'b'.
  • boolean never converts to or from a number; write flag ? 1 : 0, or count != 0, when you need one.
  • Widening conversions (byte to short to int to long to float to double, and char to int) happen on their own; int or long to float and long to double can round.
  • Narrowing needs a cast: integers keep their lowest bits, so the sign can change; floating-point values are cut toward zero and clamped to the range, and NaN becomes 0.
  • Arithmetic promotes byte, short and char to int, and compound assignment such as += casts the result back, silently.

Terms in this lesson

cast
An explicit conversion written as (type) value, such as (byte) 200 or (int) 3.99. A cast between primitive types never throws, even when the value changes: (byte) 200 is -56.
compound assignment
An assignment operator such as +=, -= or *=: b += x means b = (T) (b + x), where T is the type of b, so the result is cast back to the variable's type, and narrowed without a warning.
narrowing conversion
A conversion to a type that may not hold the value, such as int to byte or double to int. It needs a cast: integers keep their lowest bits, and floating-point values are cut toward zero and clamped to the range.
numeric promotion
The rule that converts the operands of an arithmetic operator to a common type before it computes: byte, short and char become at least int, so the sum of two bytes is an int, and an int and a long give a long.
surrogate pair
Two UTF-16 code units, a high one from D800 to DBFF and a low one from DC00 to DFFF, that together encode one character beyond U+FFFF, such as an emoji. length() counts both; codePointCount counts one.
UTF-16 code unit
One 16-bit number of the UTF-16 encoding of text, the unit a Java char holds and String.length() counts. Most characters are one code unit; characters beyond the first 65,536, such as many emoji, take two.
widening conversion
A conversion to a type that holds every value of the old one, such as int to long or char to int, which Java applies on its own. Between integer types it never changes the value; int or long to float and long to double can round.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.