SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Handling Date, Time, Text, Numeric and Boolean Values

Java Primitives, Wrapper Classes, Operators and Casting (1Z0-831)

19 min read1Z0-831 · Handling Date, Time, Text, Numeric and Boolean ValuesUpdated

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.

What you’ll learn
  • 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.

TypeSizeRange or valuesLiteral form
byte8-bit signed-128 to 127int literal that fits
short16-bit signed-32,768 to 32,767int literal that fits
char16-bit unsigned0 to 65,535'x', '\u0041', or an int literal that fits
int32-bit signedabout ±2.1 billion42 (the default for whole numbers)
long64-bit signedabout ±9.2 quintillion42L
float32-bit IEEE 754about 7 significant digits1.5f
double64-bit IEEE 754about 15–16 significant digits1.5 (the default for decimals)
booleannot specifiedtrue, falsetrue

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:

  1. If either operand is double, the other becomes double.
  2. Otherwise, if either is float, the other becomes float.
  3. Otherwise, if either is long, the other becomes long.
  4. Otherwise both become int — even two bytes, two shorts or two chars.

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;:

StatementCompiles?Reason
byte v = k;Yesk is a constant, 4 fits
byte v = k * 31;Yesconstant 124 fits in a byte
byte v = k * 32;Noconstant 128 does not fit
byte v = m;Nom is not final, so it is not a constant, even though its value fits
final long big = 7; short v = big;Nothe 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.

ConversionRuleExample
integer → smaller integerkeeps the low-order bits, so out-of-range values wrap around(byte) 250 is -6; (byte) 260 is 4
double/float → int or longtruncates 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, charfirst to int (saturating), then narrowed by keeping low-order bits(short) 4e10 is -1 (the low 16 bits of 2147483647)
double → floatrounds to the nearest float; too large becomes Infinity(float) 0.1
boolean ↔ anythingnot 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 0

Truncation 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 -30536

Note 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 18

The 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.

ExpressionInteger operandsFloating-point operands
x / 0throws ArithmeticException ("/ by zero")Infinity or -Infinity
0 / 0throws ArithmeticExceptionNaN
x % 0throws ArithmeticExceptionNaN

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 3000000000

Making 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):

LevelOperators
Postfixx++ 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");        // 100c

A 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 4

Precedence 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 null throws NullPointerException. A wrapper field defaults to null, not 0, so adding a never-assigned Integer field to an int compiles and then fails at run time when the field is unboxed.
  • Wrappers are immutable. n++ and n += 1 on an Integer unbox, compute, and box a new object into the variable; any other variable that referred to the old object still sees the old value. Passing an Integer to 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 any int going to Long, Float or Double, boxes only to Integer: Long x = 9; and Double d = 9; do not compile, so write 9L or 9.0. A small constant can still narrow and then box, so Byte bx = 10;, Short sx = 10; and Character cx = 65; compile.

The wrappers also convert text to numbers:

MethodReturnsNotes
Integer.parseInt("314")intprimitive, no object created; optional radix: parseInt("ff", 16) is 255
Integer.valueOf("314")Integerwrapper; may return a cached instance
Integer.decode("#2A")Integeris 42; understands 0x, # and leading-0 octal prefixes
Integer.getInteger("name")Integerreads a system property of that name; null if it is absent — it does not parse its argument
Boolean.parseBoolean(s)booleantrue 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.

ComparisonWhat happensExample
wrapper.equals(literal)the literal is boxed to its own type first, then types must matchLong.valueOf(9).equals(9) is false (9 boxes to Integer); .equals(9L) is true
wrapper == primitivethe wrapper is unboxed and numeric promotion appliesLong.valueOf(9) == 9 is true; Integer.valueOf(300) == 300.0 is true
wrapper == wrapper (same type)reference comparison — identity, not valueunreliable for values; use equals()
Integer == Longdoes 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 true

Integer.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.

MethodReturnsBehaviour
Math.round(double) / round(float)long / intclosest integer; ties go toward positive infinity: round(3.5) is 4, round(-3.5) is -3
Math.floor(x) / Math.ceil(x)doubleround down / up: floor(-3.5) is -4.0, ceil(-3.5) is -3.0
Math.rint(x)doubleclosest integer, ties to the even one: rint(4.5) is 4.0
Math.abs, max, minthe (promoted) argument typeabs(-12) is the int 12; max(9, 2L) is the long 9
Math.pow, Math.sqrtdoublepow(3, 2) is 9.0
Math.floorDiv, floorModthe argument type (but floorMod(long, int) returns int)division and modulus rounded toward negative infinity
Math.clamp(value, min, max)depends on overloadlimits a value to a range; see the overloads below; throws IllegalArgumentException if min > max
Math.addExact and the other exact methodsint / longthrow 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.0

Because 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.

Key takeaways
  • 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.

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?