Java Data Types, Pass-by-Value and Autoboxing
Codevity Admin · 10/25/2026
Codevity Admin · 10/25/2026
How Java stores and passes data is the source of many classic interview questions. This article covers the whole family: primitives, references, boxing, casting, and the arithmetic surprises.
Java has eight primitive types. Everything else is a reference type (a class, interface, array or enum).
| Type | Size | Notes |
|---|---|---|
byte | 8 bits | -128 to 127 |
short | 16 bits | -32,768 to 32,767 |
int | 32 bits | about -2.1 billion to 2.1 billion. The default for whole numbers |
long | 64 bits | literals end with L: 3_000_000_000L |
float | 32 bits | literals end with f |
double | 64 bits | the default for decimal numbers |
char | 16 bits | one UTF-16 code unit, written 'A' |
boolean | not fixed | true or false |
A primitive variable holds the value itself (default 0, false or '\u0000' for fields). A reference variable holds a reference to an object on the heap (default null). Because primitives are not objects, Java is not 100% object-oriented.
This is asked constantly, and "pass-by-reference for objects" is the wrong answer. Java always copies the value of the argument. For an object, that value is the reference, so the method receives a copy of the reference. Both the caller's variable and the parameter point to the same object.
public class PassByValue {
static class Point {
int x;
Point(int x) { this.x = x; }
}
static void reassign(Point p) { p = new Point(99); } // changes only the local copy of the reference
static void mutate(Point p) { p.x = 99; } // follows the reference and changes the shared object
public static void main(String[] args) {
Point point = new Point(1);
reassign(point);
System.out.println(point.x); // 1
mutate(point);
System.out.println(point.x); // 99
}
}reassign cannot make the caller's point refer to a different object, which proves the reference itself was copied. mutate can change the object, because both references reach it.
Each primitive has a wrapper class (Integer, Double, Boolean, Character and so on). Generics and collections work only with objects, so the compiler converts automatically:
Integer x = 5; becomes Integer.valueOf(5).int y = x; becomes x.intValue().That convenience has two traps.
public class Boxing {
public static void main(String[] args) {
Integer a = 127, b = 127;
Integer c = 128, d = 128;
System.out.println(a == b); // true (values from -128 to 127 come from a shared cache)
System.out.println(c == d); // false (128 is outside the cache: two different objects)
System.out.println(c.equals(d)); // true
Integer missing = null;
try {
int value = missing; // unboxing calls missing.intValue()
} catch (NullPointerException e) {
System.out.println("NullPointerException while unboxing"); // NullPointerException while unboxing
}
}
}Integer.valueOf reuses cached objects for -128 to 127, so == happens to work for small numbers and fails for larger ones. Always compare wrappers with .Performance: boxing allocates objects, so avoid wrappers in hot loops. Use primitive streams () and primitive arrays for large numeric work.
.equals()nullnullNullPointerExceptionint count = map.get("missing");IntStreamintlonglongintdoubleintMath.addExactdoublefloat0.1 + 0.20.3BigDecimalStringnew BigDecimal("0.1")double10 / 33%varPassByValuereassignp.xInteger==falseequalstrueintimport java.math.BigDecimal;
import java.util.ArrayList;
public class Types {
public static void main(String[] args) {
int big = Integer.MAX_VALUE;
System.out.println(big + 1); // -2147483648
try {
Math.addExact(big, 1);
} catch (ArithmeticException e) {
System.out.println("addExact detected the overflow"); // addExact detected the overflow
}
long total = 3_000_000_000L;
int narrowed = (int) total; // narrowing needs an explicit cast and can lose data
System.out.println(narrowed); // -1294967296
double price = 9.99;
System.out.println((int) price); // 9 (a cast truncates, it does not round)
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2"))); // 0.3
System.out.println(10 / 3 + " " + 10 % 3); // 3 1
var names = new ArrayList<String>(); // var: the compiler infers ArrayList<String>
names.add("Asha");
System.out.println(names); // [Asha]
}
}