Java Primitives, Wrapper Classes, Operators and Casting (1Z0-831)
Primitives are Java's eight built-in value types (byte, short, char, int, long, float, double and boolean), and wrapper classes such as Integer and Double are the objects that box them. Objective 1.1 of the Java SE 25 Developer exam asks you to use both and to evaluate arithmetic and boolean expressions exactly as the compiler and JVM do: which statements compile, which conversions need a cast, how precedence groups an expression, when operators short-circuit, and what the Math API returns. This lesson covers literals and ranges, numeric promotion, constant narrowing, casting, compound assignment and increments, integer and floating-point arithmetic, overflow, precedence and concatenation, boolean operators, the conditional operator, autoboxing and wrapper comparison, and the Math methods. Every code example was compiled and run on JDK 25.
On this page10 sections
- Java's eight primitive types and their literals
- Widening and binary numeric promotion
- Assignment conversion and compile-time constants
- Explicit casts: what narrowing does to the value
- Compound assignment, increment and decrement
- Arithmetic: division, remainder, overflow and floating-point values
- Operator precedence and string concatenation
- Boolean operators, short-circuiting and the conditional operator
- Wrapper classes: autoboxing, parsing and equality
- The Math API: rounding, return types and range helpers
- Write valid literals for every primitive type, including L and f suffixes, binary, octal and hex prefixes, and underscores
- Predict when a statement compiles by applying binary numeric promotion, constant narrowing and the hidden cast in compound assignment
- Work out the value produced by narrowing casts, integer division, remainder, overflow and floating-point special values
- Evaluate expressions that mix precedence, prefix and postfix increments, String concatenation and short-circuit operators
- Use wrapper classes correctly: autoboxing and unboxing, null handling, immutability, parsing methods and equals() versus ==
- Choose the right Math method and predict its return type, including round, floor, ceil, floorMod, clamp and the exact methods
Java's eight primitive types and their literals
Java has eight primitive types, and every value you write in source code is a literal of one of them. Six are numeric (four integer types and two floating-point types), plus char for UTF-16 code units and boolean, which holds only true or false and never converts to or from a number.
| Type | Size | Range or values | Literal form |
|---|---|---|---|
byte | 8-bit signed | -128 to 127 | int literal that fits |
short | 16-bit signed | -32,768 to 32,767 | int literal that fits |
char | 16-bit unsigned | 0 to 65,535 | 'x', '\u0041', or an int literal that fits |
int | 32-bit signed | about ±2.1 billion | 42 (the default for whole numbers) |
long | 64-bit signed | about ±9.2 quintillion | 42L |
float | 32-bit IEEE 754 | about 7 significant digits | 1.5f |
double | 64-bit IEEE 754 | about 15–16 significant digits | 1.5 (the default for decimals) |
boolean | not specified | true, false | true |
Three literal rules cause most compile errors. First, a whole-number literal is an int unless it ends in L, so long v = 5_000_000_000; fails with "integer number too large" even though the value fits in a long. Second, a decimal literal is a double unless it ends in f, so float f = 2.25; fails with "possible lossy conversion from double to float". Third, underscores are allowed only between digits: 12_345.6_7 is legal, while 9_999_ and 12_345_.67 (an underscore next to the decimal point) fail with "illegal underscore", and _9999 is not a number at all but an identifier.
Integer literals also have prefixes for other bases: 0b or 0B for binary (0b110 is 6), a leading 0 for octal (012 is 10, not 12) and 0x for hexadecimal (0x2F is 47). The wrapper classes expose each range as constants such as Integer.MAX_VALUE, Byte.MIN_VALUE and Character.MAX_VALUE.
Widening and binary numeric promotion
Java widens a value automatically whenever the target type can hold every value of the source type, in the order byte → short → int → long → float → double (and char → int). No cast is needed for widening, but widening to a floating-point type is not always exact: a float keeps only about 7 significant digits, so float f = 987_654_321; compiles silently and stores 987654336. Only int to float, and long to float or double, can lose precision this way; every int fits exactly in a double.
Binary numeric promotion runs before every arithmetic, comparison and bitwise operator with two numeric operands:
- If either operand is
double, the other becomesdouble. - Otherwise, if either is
float, the other becomesfloat. - Otherwise, if either is
long, the other becomeslong. - Otherwise both become
int— even twobytes, twoshorts or twochars.
The last rule is the one the exam leans on. short p = 7, q = 9; followed by short r = p + q; does not compile, because p + q is an int. The fix is to cast the whole expression, (short) (p + q); casting only an operand, as in (short) p + q, does not help, because the cast binds to p and the sum is promoted to int again. Unary - and + promote a byte, short or char operand to int as well.
char is a numeric type in this sense: 'a' + 1 is the int 98, and (char) ('a' + 1) is 'b'. A char is unsigned, so a negative constant such as char d = -1; does not compile.
Assignment conversion and compile-time constants
An assignment can narrow a value without a cast only in one case: the right-hand side is a compile-time constant expression of type int, short, char or byte, the target is byte, short or char, and the constant's value fits in the target. That is why byte b = 100; compiles even though 100 is an int literal, while byte b = 128; does not.
A compile-time constant is a literal, or an expression built only from literals and final variables of primitive (or String) type that were themselves initialised with constants. So, given final int k = 4; and int m = 4;:
| Statement | Compiles? | Reason |
|---|---|---|
byte v = k; | Yes | k is a constant, 4 fits |
byte v = k * 31; | Yes | constant 124 fits in a byte |
byte v = k * 32; | No | constant 128 does not fit |
byte v = m; | No | m is not final, so it is not a constant, even though its value fits |
final long big = 7; short v = big; | No | the rule never applies to a long source |
The rule works between the small types too: final char ch = 'A'; byte v = ch; compiles because 65 fits in a byte, and final short sh = 40; char v = sh; compiles because 40 fits in a char, but final char ch2 = 150; byte v = ch2; fails because 150 does not fit. A local variable that is final but initialised from a method call or a non-constant variable is not a constant either.
Assignment never converts between boolean and a numeric type, and never widens and then boxes: Long x = 9; fails because the int 9 cannot become a long and then a Long. The one combined step allowed is a constant narrowing followed by boxing, so Byte bx = 10;, Short sx = 10; and Character cx = 65; all compile.
Explicit casts: what narrowing does to the value
A cast such as (byte) i tells the compiler to accept a narrowing conversion, and the rules for what happens to the value depend on the source type. A cast is a unary operator, so it binds tighter than *, /, + and -: (int) 9.8 / 4 is 9 / 4, which is 2.
| Conversion | Rule | Example |
|---|---|---|
| integer → smaller integer | keeps the low-order bits, so out-of-range values wrap around | (byte) 250 is -6; (byte) 260 is 4 |
double/float → int or long | truncates toward zero; out-of-range values saturate at MIN/MAX; NaN becomes 0 | (int) -4.75 is -4; (int) 5e12 is 2147483647 |
double/float → byte, short, char | first to int (saturating), then narrowed by keeping low-order bits | (short) 4e10 is -1 (the low 16 bits of 2147483647) |
double → float | rounds to the nearest float; too large becomes Infinity | (float) 0.1 |
boolean ↔ anything | not allowed — compile error | (int) true fails |
System.out.println((byte) 250 + " " + (int) -4.75);
System.out.println((int) 5e12 + " " + (int) Float.NaN);
// -6 -4
// 2147483647 0Truncation toward zero means a negative value moves up: (int) -2.6 is -2, not -3. If you need rounding, use the Math methods covered below.
Compound assignment, increment and decrement
A compound assignment x op= y is shorthand for x = (T) (x op y), where T is the type of x — it contains a hidden narrowing cast. That is why count *= 2.5; and count -= 3L; compile for a short count while count = count * 2.5; and count = count - 3L; do not, and why byte b = 40; b *= 3; compiles while b = b * 3; does not. The hidden cast can overflow silently:
byte b = 40;
b *= 3;
short s = 30000;
s += 5000;
System.out.println(b + " " + s); // 120 -30536Note that count = (short) count * 2.5; still fails: the cast applies only to count, and the product is a double. The ++ and -- operators also keep the variable's type, so char c = 'y'; c++; leaves 'z' in c without a cast, while c = c + 1; does not compile.
Prefix and postfix differ only in the value the expression yields. ++x increments and yields the new value; x++ yields the old value and increments afterwards. Operands are evaluated left to right, each side effect happens immediately, and an assignment stores its value last:
int n = 8;
n = n++; // n++ yields 8, n becomes 9,
// then the assignment stores 8
int y = 4;
int z = y++ * 3 + ++y; // 4 * 3 + 6
System.out.println(n + " " + y + " " + z); // 8 6 18The same reasoning handles x = ++x + x++; with x = 2: the left operand makes x 3 and yields 3, the right operand yields 3 (x becomes 4), and the assignment then overwrites x with 6. To trace any such line, write down the value each operand yields from left to right and the variable's value after each step.
Arithmetic: division, remainder, overflow and floating-point values
When both operands are integers, / performs integer division that truncates toward zero, and % returns a remainder that takes the sign of the dividend (the left operand). So 9 / 4 is 2, -9 / 4 is -2, -9 % 4 is -1 and 9 % -4 is 1.
Where truncation happens depends on the operand types at each step, not on the variable that receives the result. double avg = 9 / 4; stores 2.0, because the division is done in int before the widening assignment. In 9 / 4 * 1.5 the left-to-right evaluation computes 9 / 4 = 2 first and only then multiplies by 1.5, giving 3.0.
When you need rounding toward negative infinity — for example to wrap a negative offset into a range — use Math.floorDiv and Math.floorMod: Math.floorDiv(-9, 4) is -3 and Math.floorMod(-9, 4) is 3. The result of floorMod takes the sign of the divisor, so Math.floorMod(-3, 12) is 9, whereas -3 % 12 is -3. Taking Math.abs of the operand first does not give the floor modulus either: it mirrors the value instead of wrapping it.
| Expression | Integer operands | Floating-point operands |
|---|---|---|
x / 0 | throws ArithmeticException ("/ by zero") | Infinity or -Infinity |
0 / 0 | throws ArithmeticException | NaN |
x % 0 | throws ArithmeticException | NaN |
If either operand is double or float, the whole operation is floating-point, so 5.0 / 0 is Infinity while 5 / 0 throws at run time.
Floating-point arithmetic never throws; it produces special values instead. NaN ("not a number") is the result of undefined operations such as 0.0 / 0, and it is not equal to anything, including itself, under ==. Use Double.isNaN(x) to test for it. Double.equals deliberately treats NaN as equal to NaN (and distinguishes 0.0 from -0.0), so comparing two NaN values with == gives false while comparing them as boxed Doubles with equals gives true.
Decimal fractions such as 0.1 have no exact binary representation, so 0.1 + 0.7 prints 0.7999999999999999 and 0.1 + 0.7 == 0.8 is false. Compare floating-point results with a tolerance, or use BigDecimal for exact decimal values such as money.
Integer overflow wraps silently. Integer.MAX_VALUE + 1 is Integer.MIN_VALUE, and the absolute value of a type's MIN_VALUE has no positive counterpart, so Math.abs(Long.MIN_VALUE) returns Long.MIN_VALUE unchanged, still negative. Overflow happens in the type the expression is computed in, before any assignment:
long a = 1_000_000 * 3_000; // int math: overflows
long b = 1_000_000L * 3_000; // long math from the start
System.out.println(a + " " + b);
// -1294967296 3000000000Making the first operand a long fixes it, because promotion then applies to every step. When overflow must be an error rather than a wrong number, use the exact methods: Math.addExact, subtractExact, multiplyExact, incrementExact, negateExact and toIntExact throw ArithmeticException instead of wrapping (the message is "integer overflow" for the int overloads and "long overflow" for the long ones).
Operator precedence and string concatenation
Precedence decides how an expression is grouped; evaluation is then left to right. Operators higher in this table bind tighter, and operators on the same row group left to right (except assignment and ?:, which group right to left):
| Level | Operators |
|---|---|
| Postfix | x++ x-- |
| Unary | ++x --x + - ! ~ (type) |
| Multiplicative | * / % |
| Additive | + - |
| Shift | << >> >>> |
| Relational | < > <= >= instanceof |
| Equality | == != |
| Bitwise / logical | &, then ^, then | |
| Conditional | &&, then || |
| Ternary | ? : |
| Assignment | = += -= *= and the other compound forms |
So 10 - 6 / 4 * 3 % 5 groups as 10 - (((6 / 4) * 3) % 5): the multiplicative operators share a level and run left to right, giving 1, then 3, then 3, and 10 - 3 is 7. Parentheses always override precedence.
The + operator is numeric addition when both operands are numeric (including char) and String concatenation as soon as either operand is a String. Because + groups left to right, everything before the first String is added as numbers and everything after it is appended as text:
System.out.println(4 + 5 + "x" + 4 + 5); // 9x45
System.out.println('b' + 2 + "c"); // 100cA char in the numeric part contributes its code ('b' is 98); once the expression has become a String, a char is appended as a character. String methods themselves belong to the next lesson on String and StringBuilder.
Boolean operators, short-circuiting and the conditional operator
&& and || are short-circuit operators: the right operand is evaluated only if the left one does not already decide the result. &, | and ^ applied to boolean operands are logical AND, OR and XOR that always evaluate both sides. The difference is visible only when the right operand has a side effect:
int j = 3;
boolean p = j < 0 && ++j > 0; // right side skipped
boolean q = j < 0 | ++j > 0; // right side runs
System.out.println(p + " " + q + " " + j); // false true 4Precedence matters when operators are mixed: && binds tighter than ||, so a || b && c means a || (b && c). Group first, then short-circuit: if a is true, the whole (b && c) group is skipped, including every increment inside it. ! negates a boolean, and ^ is true only when exactly one operand is true. Conditions in Java must be boolean; an int is never treated as true or false.
The conditional operator cond ? a : b evaluates only the chosen branch, but its type is decided at compile time from both branches. When both branches are numeric, binary numeric promotion applies to the whole expression, so Object o = ready ? 7 : 3.5f; stores the Float 7.0 even when the int branch is chosen. When the branches are unrelated reference types (say an Integer and a String), no promotion happens and the selected object is stored unchanged.
Wrapper classes: autoboxing, parsing and equality
Each primitive has a wrapper class in java.lang — Byte, Short, Integer, Long, Float, Double, Character and Boolean — that lets the value be used where an object is required, such as in collections and generics. Autoboxing converts a primitive to its wrapper automatically (Integer n = 5;) and unboxing converts back (int k = n;), including inside arithmetic and comparisons.
Three behaviours follow from wrappers being objects:
- Unboxing
nullthrowsNullPointerException. A wrapper field defaults tonull, not 0, so adding a never-assignedIntegerfield to anintcompiles and then fails at run time when the field is unboxed. - Wrappers are immutable.
n++andn += 1on anIntegerunbox, compute, and box a new object into the variable; any other variable that referred to the old object still sees the old value. Passing anIntegerto a method and incrementing the parameter therefore never changes the caller's variable — Java passes the reference by value. - Boxing does not widen. A non-constant
int, or anyintgoing toLong,FloatorDouble, boxes only toInteger:Long x = 9;andDouble d = 9;do not compile, so write9Lor9.0. A small constant can still narrow and then box, soByte bx = 10;,Short sx = 10;andCharacter cx = 65;compile.
The wrappers also convert text to numbers:
| Method | Returns | Notes |
|---|---|---|
Integer.parseInt("314") | int | primitive, no object created; optional radix: parseInt("ff", 16) is 255 |
Integer.valueOf("314") | Integer | wrapper; may return a cached instance |
Integer.decode("#2A") | Integer | is 42; understands 0x, # and leading-0 octal prefixes |
Integer.getInteger("name") | Integer | reads a system property of that name; null if it is absent — it does not parse its argument |
Boolean.parseBoolean(s) | boolean | true only for "true" in any letter case; anything else, including "yes" and null, is false |
The numeric parse methods throw NumberFormatException for text that is not a valid number of that type, such as Integer.parseInt("1.5") or an empty string. Each wrapper also offers MIN_VALUE/MAX_VALUE constants and helpers such as Integer.compare, Integer.toBinaryString and Character.isLetter.
equals() on a wrapper returns true only when the argument is the same wrapper type holding the same value; == compares numbers when one side is a primitive, and compares references when both sides are wrappers.
| Comparison | What happens | Example |
|---|---|---|
wrapper.equals(literal) | the literal is boxed to its own type first, then types must match | Long.valueOf(9).equals(9) is false (9 boxes to Integer); .equals(9L) is true |
wrapper == primitive | the wrapper is unboxed and numeric promotion applies | Long.valueOf(9) == 9 is true; Integer.valueOf(300) == 300.0 is true |
wrapper == wrapper (same type) | reference comparison — identity, not value | unreliable for values; use equals() |
Integer == Long | does not compile ("incomparable types") | convert one side explicitly |
Long big = 9L;
Integer small = 9;
System.out.println(big.equals(9) + " " + big.equals(9L)
+ " " + (big == 9) + " " + small.equals(9));
// false true true trueInteger.valueOf is guaranteed to cache values from -128 to 127, so == between two boxed Integers in that range happens to be true; outside it the result depends on the JVM's cache settings. Never rely on == between two wrapper objects to compare values.
The Math API: rounding, return types and range helpers
The java.lang.Math methods are static and their return types are part of the answer, because they decide how the rest of an expression is promoted and printed.
| Method | Returns | Behaviour |
|---|---|---|
Math.round(double) / round(float) | long / int | closest integer; ties go toward positive infinity: round(3.5) is 4, round(-3.5) is -3 |
Math.floor(x) / Math.ceil(x) | double | round down / up: floor(-3.5) is -4.0, ceil(-3.5) is -3.0 |
Math.rint(x) | double | closest integer, ties to the even one: rint(4.5) is 4.0 |
Math.abs, max, min | the (promoted) argument type | abs(-12) is the int 12; max(9, 2L) is the long 9 |
Math.pow, Math.sqrt | double | pow(3, 2) is 9.0 |
Math.floorDiv, floorMod | the argument type (but floorMod(long, int) returns int) | division and modulus rounded toward negative infinity |
Math.clamp(value, min, max) | depends on overload | limits a value to a range; see the overloads below; throws IllegalArgumentException if min > max |
Math.addExact and the other exact methods | int / long | throw ArithmeticException on overflow |
System.out.println(Math.round(3.5) + " " + Math.round(-3.5)
+ " " + Math.floor(-3.5) + " " + Math.ceil(-3.5));
// 4 -3 -4.0 -3.0Because floor and ceil return double, a single call turns a whole sum into a double: with var a = Math.round(2.5f) (the int 3), var b = Math.ceil(-1.2) (-1.0) and var c = Math.abs(-4) (4), a + b + c prints 6.0, not 6.
Math.clamp was added in Java 21 and has exactly four overloads: clamp(long, int, int) returning int, clamp(long, long, long) returning long, clamp(double, double, double) and clamp(float, float, float). There is no clamp(int, int, int): an int value widens to long and uses the first form, which still returns int. So int level = Math.clamp(-40L, 0, 10); compiles without a cast and stores 0.
Tip. Objective 1.1 is tested almost entirely through code. Expect short programs that ask what is printed, which statements compile or which change makes code compile, with 'Compilation fails' and runtime exceptions such as ArithmeticException and NullPointerException among the options. Items combine two or more rules in one line: promotion plus a narrowing assignment, postfix increments inside a short-circuited condition, integer division before a widening assignment, or a wrapper compared with equals() against a literal of another type. Trace each expression in the order the compiler groups it and the JVM evaluates it, and keep track of each intermediate value's type as well as its value, because the type decides whether the code compiles and how the result prints.
- A whole-number literal is an int and a decimal literal is a double unless it ends in L or f; a leading 0 means octal.
- byte, short and char operands are promoted to int before arithmetic, so the result needs a cast to go back to the narrow type.
- Without a cast, only a compile-time constant of type int or smaller that fits can be assigned to byte, short or char.
- Compound assignment (+=, *=, ...) and ++/-- carry a hidden cast to the variable's type, so they compile where the long form does not and can overflow silently.
- Integer casts keep the low-order bits; floating-to-integer casts truncate toward zero, saturate at the type's limits and turn NaN into 0.
- Integer division truncates toward zero, % takes the dividend's sign, and integer division by zero throws while floating-point division gives Infinity or NaN.
- int arithmetic wraps on overflow before any assignment to long; make the first operand long, or use Math.addExact and friends to get an exception.
- && and || can skip their right operand; & and | always evaluate both; && binds tighter than ||.
- Unboxing null throws NullPointerException, wrappers are immutable, and wrapper equals() is false for a different wrapper type.
- Math.round returns long for a double and int for a float and breaks ties upward; floor and ceil return double.
Frequently asked questions
Why does byte + byte not compile into a byte in Java?
Java applies binary numeric promotion before arithmetic, and that promotes byte, short and char operands to int. The result of adding two bytes is therefore an int, and assigning an int expression that is not a compile-time constant to a byte needs an explicit cast around the whole expression, such as (byte) (a + b). A compound assignment like b += a also works, because it includes an implicit cast back to byte.
What is the difference between x++ and ++x in Java?
Both add one to x, but they yield different values to the surrounding expression. The postfix form x++ yields the value x had before the increment, while the prefix form ++x increments first and yields the new value. Because the assignment in x = x++ stores the value the postfix expression yielded, that statement leaves x unchanged.
Does dividing by zero always throw an exception in Java?
No. Integer division or remainder by zero throws ArithmeticException with the message '/ by zero'. If either operand is a float or double, the operation is floating-point and never throws: a nonzero value divided by zero gives Infinity or -Infinity, and 0.0 / 0 or any remainder by zero gives NaN.
Why is Long.valueOf(9).equals(9) false in Java?
Wrapper equals() methods return true only when the argument is the same wrapper type with the same value. The literal 9 is an int, so it is autoboxed to an Integer, and Long.equals of an Integer is false. Passing 9L boxes to a Long and returns true. Comparing with == against a primitive, as in Long.valueOf(9) == 9, unboxes the Long and compares the numbers, so it is true.
How does Math.round handle negative halves in Java?
Math.round returns the closest integer and breaks ties toward positive infinity, so Math.round(3.5) is 4 and Math.round(-3.5) is -3, while Math.round(-3.6) is -4. It returns a long for a double argument and an int for a float argument. Math.rint is different: it breaks ties to the even integer and returns a double.
When can an int be assigned to a byte without a cast?
Only when the value is a compile-time constant expression of type int, short, char or byte and it fits in the byte range of -128 to 127. Literals and final variables initialised with constants qualify, so final int limit = 90; byte b = limit; compiles. A non-final variable, a long constant, or a constant outside the range always needs an explicit cast.
What is the difference between Integer.parseInt and Integer.valueOf?
Integer.parseInt(String) returns a primitive int, while Integer.valueOf(String) returns an Integer object that may come from the wrapper cache. Both throw NumberFormatException for text that is not a valid integer. Integer.decode also returns an Integer and accepts hex and octal prefixes, and Integer.getInteger reads a system property rather than parsing its argument.
Source
This lesson covers the "Handling Date, Time, Text, Numeric and Boolean Values" domain of the official 1Z0-831 exam guide. Vendors revise their guides — check the source for the current version.
- Java SE 25 Developer Professional (1Z0-831) exam page and topics — Oracle University
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.