# Data types

Source: https://codewiki.com/java/data-types/

> - **what**: Every Java value has either a primitive or reference type. Primitives directly represent Boolean, integer, floating-point, or UTF-16 code-unit values; a reference points to an object or array, or is `null`.
> - **trap**: Widening can still lose precision and narrowing can truncate or wrap. Wrapper types add null-unboxing and reference-equality hazards.
> - **fix**: Choose types from the value range and missing-value semantics, validate conversions at boundaries, and express domain constraints with `BigDecimal`, `Math.*Exact`, and value equality.

## What it is and why it exists

A data type defines what a value can represent and which operations accept it. Java is statically typed: before code runs, the compiler checks whether assignments, method calls, and expressions can be combined under the language rules. A variable's type does not change when it later holds a different value; `var` merely asks the compiler to infer one fixed local-variable type from the initializer.

Java divides types into primitive types and reference types. The eight primitive types are built-in value categories; classes, interfaces, arrays, and type variables are reference types. A primitive variable cannot hold `null`, while a reference variable holds a reference to an object or array, or the null reference.

You encounter this distinction at almost every API boundary. Arithmetic expressions apply numeric promotion, generic collections require wrapper types, database or deserialized data may produce `null`, and text processing must distinguish a `char` from a complete Unicode code point. Knowing the rules lets you ask whether a compiler-approved conversion also preserves the business meaning.

### The eight primitive types

Java specifies the widths and ranges of its integral types, so those semantics do not change with the processor platform. A `boolean` has only the values `true` and `false`; the Java Language Specification does not prescribe its storage size in an object layout.

| Type | Category | Width | Values or meaning |
| --- | --- | ---: | --- |
| `byte` | Signed integer | 8 | −128 through 127 |
| `short` | Signed integer | 16 | −32,768 through 32,767 |
| `int` | Signed integer | 32 | −2³¹ through 2³¹−1 |
| `long` | Signed integer | 64 | −2⁶³ through 2⁶³−1 |
| `char` | Unsigned integer | 16 | One UTF-16 code unit, 0 through 65,535 |
| `float` | IEEE 754 binary floating point | 32 | Finite values, signed zeros, infinities, and NaN |
| `double` | IEEE 754 binary floating point | 64 | Finite values, signed zeros, infinities, and NaN |
| `boolean` | Boolean | Unspecified | `true` or `false` |

`int` is usually the default integer choice, and an integer literal also has type `int` by default. Integer literals outside that range normally need an `L` suffix to become `long`. Floating-point literals default to `double`; assigning one to `float` requires an `F` suffix or a cast. Suffixes select literal types, but they do not prevent runtime arithmetic from overflowing or rounding.

### Reference types and objects

A reference value is not the object's contents. Assigning an array, string, or ordinary object reference to another variable makes both variables point to the same object. If the object is mutable, a change through one reference is visible through the other. Java passes every argument by value; for a reference type, the copied value is the reference itself.

An array is an object even when its elements are primitives. An `int[]` has `int` elements, whereas an `Integer[]` has reference elements and each element may be `null`. Generic type arguments cannot be primitive types, so `List<int>` is illegal and a general-purpose integer collection uses `List`.

### Defaults belong only to certain storage locations

Instance fields, static fields, and elements of a newly created array receive default values. Numeric primitives default to zero, `char` to `\u0000`, `boolean` to `false`, and reference types to `null`. These defaults give objects and arrays a defined state immediately after construction.

Local variables have no default value. The compiler performs definite-assignment analysis, and reading a local that is not certainly assigned on that path is a compile-time error. This rule stops a method from treating uninitialized stack state as a valid business value.

### Select types from a contract

A type choice starts with the permitted value set, not the smallest representation that fits today's sample. Use `int` for ordinary bounded counts and `long` when the documented range requires it. If overflow is an error rather than modular arithmetic, the contract also needs validation or exact operations; choosing a wider type alone only moves the boundary.

Absence is a separate decision from numeric range. A primitive says every instance has a value, while a wrapper can encode absence with `null` but requires every consumer to handle that state. A sentinel such as `-1` is safe only when the domain excludes it and the API documents its meaning.

Some data that looks numeric is not a quantity. Account numbers, postal codes, and externally assigned identifiers may need leading zeros, formatting, or more digits than arithmetic types provide. Represent them as text or a domain type unless arithmetic operations are part of their contract.

Use `byte[]` for encoded bytes and `char` or `String` for UTF-16 text, but do not convert between them by casting. Text-to-byte conversion requires an explicit character encoding such as UTF-8. That boundary belongs in an encoder or decoder API where malformed-input behavior can be chosen deliberately.

## How it works

### Declarations, assignments, and calls

A variable declaration establishes a static type, and an assignment expression must produce that type through an assignment conversion. Method invocation uses method-invocation conversions to determine which overloads apply. A cast unlocks additional conversions, but it only asks Java to change a representation or check a reference type according to fixed rules; it does not prove that the result fits the business range.

A reference assignment can widen, such as assigning an `ArrayList` reference to a `List` variable. A downcast is explicit and checks at runtime whether the object belongs to the target type; failure throws `ClassCastException`. Pattern matching for `instanceof` combines the test with a safe local binding.

### Conversion phases in overloaded calls

Overload resolution considers conversions in phases. Fixed-arity candidates that need only identity, widening primitive, or widening reference conversions are considered before candidates that require boxing or unboxing. Variable-arity candidates are considered only after the fixed-arity phases fail.

This ordering means adding a wrapper overload can change which generated call is selected, even though each method would compile by itself. A primitive widening overload may win before a boxing overload, while an existing wrapper reference may widen to `Object` in an earlier phase than it could unbox for a primitive parameter. Read the declared argument type as well as its runtime value when checking a call.

The null literal can be passed to reference parameters but not primitive parameters. If unrelated reference overloads are equally specific, a call with bare `null` can become ambiguous at compile time. Avoid overload families whose only distinction is unrelated wrapper types; a domain-specific method name usually expresses the contract more clearly.

### Widening and narrowing primitive conversions

A widening primitive conversion sends a value to a target type allowed by the language. `byte`, `short`, and `char` can widen to `int`, and integral types can continue through the allowed wider integral or floating-point types. Widening an integer to a wider integer preserves the value, but `int` to `float`, `long` to `float`, and `long` to `double` can lose precision because the target has too few significant bits.

A narrowing conversion requires an explicit cast except for a few constant-assignment rules. Narrowing an integer discards high bits and can change the sign. Converting floating point to an integer first rounds toward zero, then applies the specified results for NaN and out-of-range values. An ordinary numeric cast does not throw merely because the source is outside the target range.

The common widening paths are below. An arrow implied by the table means a widening conversion exists, not that every step preserves numeric precision.

| Source | Permitted widening targets |
| --- | --- |
| `byte` | `short`, `int`, `long`, `float`, `double` |
| `short` | `int`, `long`, `float`, `double` |
| `char` | `int`, `long`, `float`, `double` |
| `int` | `long`, `float`, `double` |
| `long` | `float`, `double` |
| `float` | `double` |

`boolean` does not participate in numeric conversion. Java neither accepts `0` and `1` as Boolean values nor permits casts between Boolean and integral types. This keeps the intent of condition expressions explicit.

### Numeric promotion and intermediate results

Unary and binary numeric operations perform numeric promotion. `byte`, `short`, and `char` are promoted to `int` for most arithmetic, so even the type of two added `byte` values is `int`. Mixed numeric operands use binary numeric promotion to select a common type.

A wider result variable cannot rescue an intermediate result that already overflowed. In `long total = intPrice * intQuantity`, multiplication finishes as `int` before the result is widened to `long`. Widening one operand before multiplication, or using `Math.multiplyExact`, changes the range of the multiplication itself or turns overflow into an exception.

Compound assignment also includes implicit narrowing. `count += delta` is roughly equivalent to `count = (T) (count + delta)`, where `T` is the left-hand type, except that the left side is evaluated only once. A `byte` `+=` expression can therefore compile and still wrap silently.

### Boxing, unboxing, and `null`

Autoboxing converts a primitive value to the corresponding wrapper reference, such as `int` to `Integer`. Unboxing performs the reverse conversion. The compiler inserts these conversions when generic collections, assignments, calls, or expressions require them.

A wrapper object has reference identity, but most application code cares about the wrapped value. `==` on two `Integer` references compares identity unless the expression first triggers unboxing. The specification guarantees identity for some boxed small constants, which can make broken code appear correct with small test data. Use `equals` or `Objects.equals` to compare wrapper values.

Unboxing a null wrapper reference throws `NullPointerException`. The unboxing can be hidden inside arithmetic, a relational comparison, a conditional expression, a method argument, or an enhanced `for` loop. An API must state whether a missing value is allowed, then validate it, supply a domain-meaningful default, or preserve the reference form before unboxing.

### Floating point, decimals, and text

`float` and `double` use binary floating-point representations. Many decimal fractions have no finite binary representation, so the result of `0.1 + 0.2` is not the same `double` as `0.3`. These types suit measurements, scientific calculations, and values with an accepted error model, but they usually do not suit money that requires exact decimal values and a fixed rounding policy.

`BigDecimal` represents a decimal with an arbitrary-precision integer and a scale. Use the string constructor when the input is external decimal text; `new BigDecimal(0.1)` faithfully captures the already inexact `double`. Division may need an explicit precision or rounding mode. `equals` also compares scale, while `compareTo` compares numeric order.

A `char` represents one UTF-16 code unit, not necessarily one complete user-visible character. A Unicode code point outside the Basic Multilingual Plane uses a surrogate pair, so a string's `length()` can differ from its code-point count. Use `codePoints()`, `codePointAt`, or code-point offset APIs when you need to traverse complete code points.

### Arrays preserve runtime component types

An array object remembers its component type at runtime. Java arrays are covariant, so a `String[]` can be assigned to an `Object[]` variable, but the underlying object is still a string array. Storing a non-string through that broader reference compiles and then throws `ArrayStoreException`.

Generic collections make a different tradeoff. `List` is not a subtype of `List`, so the analogous unsafe write is rejected at compile time. Generic type arguments are erased in ordinary Java generics, while an array's component-type check remains reified at runtime.

Primitive arrays also avoid per-element boxing semantics. An `int[]` element always has an `int` value and begins at zero; an `Integer[]` element is a reference and begins at `null`. Pick between them from the API's need for generic interoperability and missing values, then measure performance only if it matters.

## Examples

### Primitives and references

<!-- quick -->

```java
// file: PrimitiveValues.java
public class PrimitiveValues {
    public static void main(String[] args) {
        byte warehouseZone = 12;
        int unitsInStock = 2_400;
        long orderId = 3_000_000_000L;
        double packageWeightKg = 1.75;
        char currencySymbol = '€';
        boolean paid = true;

        int[] dailyOrders = {18, 21, 16};
        int[] sameOrders = dailyOrders; // Copies the reference, not the array elements.
        sameOrders[0] = 20;

        System.out.println("zone=" + warehouseZone + ", stock=" + unitsInStock);
        System.out.println("order=" + orderId + ", weight=" + packageWeightKg);
        System.out.println("currencyUnit=" + (int) currencySymbol + ", paid=" + paid);
        System.out.println("firstDay=" + dailyOrders[0]);
    }
}
```

```text
zone=12, stock=2400
order=3000000000, weight=1.75
currencyUnit=8364, paid=true
firstDay=20
```

<!-- /quick -->

The literal suffixes give `orderId` and `packageWeightKg` the required types. The last line demonstrates that array variables hold references: changing the element through `sameOrders` makes the same change visible through `dailyOrders`.

### Conversions and overflow checks

```java
// file: NumericConversions.java
public class NumericConversions {
    public static void main(String[] args) {
        int exactInteger = 16_777_217;
        float roundedFloat = exactInteger; // Widens the type but loses one unit of precision.

        int shipmentCode = 130;
        byte narrowedCode = (byte) shipmentCode; // Keeps only the low eight bits.

        byte packedCount = 12;
        packedCount += 120; // Includes an implicit narrowing conversion.

        System.out.printf("int=%d float=%.0f%n", exactInteger, roundedFloat);
        System.out.println("narrowed=" + narrowedCode + ", compound=" + packedCount);

        try {
            Math.toIntExact(3_000_000_000L);
        } catch (ArithmeticException error) {
            System.out.println("overflow detected");
        }
    }
}
```

```text
int=16777217 float=16777216
narrowed=-126, compound=-124
overflow detected
```

The first line shows that widening does not imply exact conversion. The two `byte` results come from retaining low bits rather than throwing. `Math.toIntExact` fits a boundary that must reject an out-of-range value.

### Wrappers and missing values

```java
// file: BoxingValues.java
import java.util.List;
import java.util.Objects;

public class BoxingValues {
    public static void main(String[] args) {
        List<Integer> quantities = List.of(2, 3, 4); // Adding elements requires boxing.

        int total = 0;
        for (int quantity : quantities) { // Reading each element requires unboxing.
            total = Math.addExact(total, quantity);
        }

        Integer expected = 9;
        Integer actual = Integer.valueOf(total);
        Integer missing = null;

        System.out.println("total=" + total);
        System.out.println("matches=" + Objects.equals(expected, actual));
        System.out.println("fallback=" + Objects.requireNonNullElse(missing, 0));
    }
}
```

```text
total=9
matches=true
fallback=0
```

The collection's element type must be `Integer`, while the accumulator can remain an `int`. `Objects.equals` handles value comparison and null references. A default is appropriate only when the domain genuinely interprets absence as zero.

### Exact decimals and Unicode code points

```java
// file: DecimalAndText.java
import java.math.BigDecimal;
import java.math.RoundingMode;

public class DecimalAndText {
    public static void main(String[] args) {
        BigDecimal unitPrice = new BigDecimal("19.95");
        BigDecimal quantity = BigDecimal.valueOf(3);
        BigDecimal taxRate = new BigDecimal("0.075");

        BigDecimal subtotal = unitPrice.multiply(quantity);
        BigDecimal tax = subtotal.multiply(taxRate)
                .setScale(2, RoundingMode.HALF_UP);

        BigDecimal oneDecimal = new BigDecimal("1.0");
        BigDecimal twoDecimals = new BigDecimal("1.00");
        String vehicle = "🚗";

        System.out.println("subtotal=" + subtotal + ", tax=" + tax);
        System.out.println("numericEqual=" + (oneDecimal.compareTo(twoDecimals) == 0));
        System.out.println("equals=" + oneDecimal.equals(twoDecimals));
        System.out.println("units=" + vehicle.length()
                + ", codePoints=" + vehicle.codePointCount(0, vehicle.length()));
    }
}
```

```text
subtotal=59.85, tax=4.49
numericEqual=true
equals=false
units=2, codePoints=1
```

The money enters `BigDecimal` from decimal strings, and the rounding rule appears only at the boundary where tax needs two fractional digits. The other lines expose `BigDecimal`'s scale semantics and the difference between UTF-16 code-unit and Unicode code-point counts.

## Pitfalls

> **Pitfall:** Applying field and array default rules to a local variable produces code that does not compile. Fix it by explicitly initializing the local on every control-flow path. Do not insert an arbitrary `0` or `false` merely to satisfy the compiler unless that value represents a valid state.

> **Pitfall:** Storing a result in `long` does not make an earlier `int` multiplication run with 64-bit range, and an explicit narrowing cast does not validate the range. Widen before the operation and use `Math.addExact`, `multiplyExact`, `toIntExact`, or an explicit boundary check when the domain rejects wraparound.

> **Pitfall:** Representing money with `double`, then comparing every result with one fixed epsilon, hides a decimal rounding policy inside incidental error. Use `BigDecimal` constructed from decimal text and state the scale and `RoundingMode`. For measurements, derive the tolerance from the domain's scale rather than a universal constant.

> **Pitfall:** Wrapper `==` can pass with small test integers and fail with other values. Calling `equals` on `null` or triggering unboxing also throws. Use `Objects.equals` for nullable wrapper values and define null, default, and rejection policies at the API boundary.

> **Pitfall:** Treating one `char` as one character splits surrogate pairs for supplementary Unicode code points and can miscount text. First decide whether the requirement concerns UTF-16 code units, Unicode code points, or user-perceived graphemes. Use the `String` code-point APIs for code-point processing.

<!-- deep -->

## Conversion boundaries in detail

### Constant-expression narrowing assignments

The integer literal `127` has type `int`, yet `byte limit = 127` compiles. Assignment conversion may narrow an `int` constant expression to `byte`, `short`, or `char` when its value is representable in the target. This is an exception proved safe at compile time, not a general conversion between runtime integer variables.

If you first write `int limitValue = 127` and then assign `limitValue` to a `byte`, a cast is required because the expression no longer meets the relevant constant-expression rule. A `final int limitValue = 127` can remain a constant variable when it satisfies the constant-variable rules. In generated-code reviews, distinguish a compile-time proof from runtime data.

### Defined floating-to-integral results

When floating point narrows to an integer, Java does not use a caller-selected rounding mode. A finite value first rounds toward zero. A value outside the target integer range becomes the corresponding minimum or maximum, and NaN becomes zero. Narrowing farther to `byte` or `short` then discards high bits.

That determinism is not business safety. Casting an unvalidated `double` to an order quantity can quietly turn NaN into `0` or positive infinity into the largest integer. If those inputs must be rejected, check `Double.isFinite` and the explicit range before converting.

### The minimum boxing-identity guarantee

For certain compile-time constants, the specification requires repeated boxing to produce `==`-identical references. These include Boolean values, `char` from `\u0000` through `\u007f`, and integral values from −128 through 127. An implementation may cache more values, so identity outside the minimum range is not a portable assumption.

This is why wrapper `==` defects often escape unit tests. If every test value is inside the minimum cache range, identity comparison happens to match the expected value comparison. Express value semantics with `equals`, `Objects.equals`, or an intentional comparison after unboxing.

### `BigDecimal` value and representation

A `BigDecimal` value contains an unscaled integer and a scale, so `1.0` and `1.00` have the same numeric ordering but different representations. `compareTo` returns zero for numeric equality; `equals` is true only when both value and scale agree. The distinction matters when `BigDecimal` is a `HashMap` key because hash equality follows `equals`.

The calculation policy must also define division and rounding boundaries. A division with no exact terminating result throws `ArithmeticException` when no rounding context is supplied. Do not let generated code pick `HALF_UP` or two decimal places arbitrarily; the currency, tax rule, or domain protocol decides the scale and rounding mode.

### Runtime checks for reference casts

A reference cast does not transform an object into an instance of another class. It changes the static view after a runtime compatibility check. Casting an `Object` that actually refers to a `Long` into `Integer` fails even when both wrappers hold numerically comparable values.

Use `instanceof` when several runtime variants are an expected part of the input contract. Use polymorphism or a sealed hierarchy when the variants belong to the domain model. Catching `ClassCastException` after speculative casts usually hides a boundary that should have been checked or typed explicitly.

`null` behaves differently: casting `null` to a reference type succeeds and still yields `null`. A successful cast therefore proves type compatibility only for a non-null object; it does not prove presence. Keep the null check visible when the following operation dereferences the result.

Reflection and deserialization APIs often expose values as `Object`, which invites generated casts based on example payloads. Verify the producer's declared schema and all supported numeric wrapper classes before casting. Converting through the `Number` API may still narrow or round, so it also needs a range policy.

### Signed zero, infinity, and NaN

Floating-point types include positive zero, negative zero, positive and negative infinity, and NaN. Ordinary `==` treats the two zeros as equal, while reciprocals can reveal their signs. NaN compares unequal to every value, including itself, so test it with `Float.isNaN` or `Double.isNaN`.

Division by floating-point zero produces an infinity or NaN according to IEEE 754 semantics; it does not throw the integer `ArithmeticException` used for division by integer zero. Generated validation that merely catches `ArithmeticException` therefore does not reject non-finite floating-point results. Use `isFinite` when an API accepts only ordinary finite measurements.

Relational operators do not provide a useful total ordering when NaN is present. Sorting and ordered containers should use the library comparison contract, such as `Double.compare` or `Double.compareTo`, and the application should decide whether non-finite values are valid at all. Do not improvise an ordering with subtraction and a cast.

`BigDecimal` has no NaN or infinity values. That makes invalid decimal input fail rather than propagate as a special numeric value, but it does not remove the need for bounds, scale, and rounding rules. Exact representation and valid business input are separate guarantees.

### Type rules versus runtime layout

The language specification fixes value ranges and conversion semantics, but it does not promise that a local variable or wrapper object occupies a particular number of bytes on a given JVM. Object headers, reference widths, alignment, and JIT optimization are implementation details. Without a measurement on the target JVM, do not derive whole-object sizes or performance multiples from primitive widths.

Likewise, autoboxing's source-level semantics do not guarantee that every operation allocates a distinct observable object. The compiler and JVM may eliminate allocations when observable behavior is unchanged. Measure performance-sensitive code with an appropriate benchmark, but never make correctness depend on whether that optimization occurs.

When a wire format or file layout requires an exact width, use the format's byte-level contract rather than an assumed JVM object layout. `ByteBuffer` and explicit encoders make byte order, width, and failure handling visible.

When an API only needs value semantics, expose primitives or immutable domain values instead of wrapper identity. This keeps implementation caching and allocation decisions outside the contract.

<!-- /deep -->

[Checkpoint: java/data-types](https://codewiki.com/java/data-types/#checkpoint)

## Further reading

- [Java Language Specification: types, values, and variables](https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html)
- [Java Language Specification: conversions and contexts](https://docs.oracle.com/javase/specs/jls/se25/html/jls-5.html)
- [Java Language Specification: expressions](https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html)
- [Java SE 25 API: `BigDecimal`](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/math/BigDecimal.html)
