skip to content

How is the Integer cache implemented internally, and what is and isn't configurable about it?

level: seniorimportance: should knowfreq 45%

answer

  1. private static IntegerCache: array low=-128..high
  2. valueOf indexes cache[i+128] in range else new
  3. low fixed, high raisable via -XX:AutoBoxCacheMax
  4. Long/Short/Byte cache -128..127 (no knob), Char 0..127
  5. Float/Double not cached

basics

~20 s

Integer holds a static IntegerCache: an array of pre-built Integer objects for -128 up to a high bound (default 127). valueOf indexes into it. The low bound -128 is fixed; only the high bound is configurable via a JVM flag.

solid answer

~40 s

Integer.IntegerCache is a private static nested class initialized at class load. It allocates an array of Integer instances covering low=-128 to a high bound that defaults to 127. Integer.valueOf(i) checks if i is within [low, high]; if so it returns cache[i + 128], otherwise it does new Integer(i). The array is built once and eagerly, so the small flyweights are shared process-wide. The low bound is hard-coded at -128 and cannot be changed. The high bound can be raised (never lowered below 127) via -XX:AutoBoxCacheMax=N or the property java.lang.Integer.IntegerCache.high; this lets you cache more values. Long/Short/Byte have a fixed -128..127 cache with no such knob; Character caches 0..127. Raising the cache only saves allocations — it must never be used to make == comparisons 'work', since that couples correctness to a JVM flag.

code

java · 18 lines
java
// Conceptual sketch of the JDK's Integer.valueOf + IntegerCache
private static final class IntegerCache {
    static final int low = -128;          // fixed
    static final int high;                // default 127, raisable via -XX:AutoBoxCacheMax
    static final Integer[] cache;
    static {
        int h = 127; // ... possibly overridden by java.lang.Integer.IntegerCache.high
        high = h;
        cache = new Integer[(high - low) + 1];
        for (int k = 0, v = low; k < cache.length; k++, v++) cache[k] = new Integer(v);
    }
}

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i - IntegerCache.low]; // shared flyweight
    return new Integer(i);                                 // fresh object
}

go deeper

for a junior

Knows there is a cache for small Integers and that it defaults to -128..127.

for a middle

Can describe valueOf checking the range and returning cached vs new objects, and names other cached wrappers.

for a senior

Explains the IntegerCache structure, the fixed-low/raisable-high asymmetry and the flag, which wrappers cache and which don't, and why the flag is a tuning—not correctness—lever.

for a principal

Reasons about class-init eager allocation, startup-memory tradeoffs of widening the cache, governance around relying on it, and JVM-version portability of the behavior.

## The data structure Inside `java.lang.Integer` there is a **private static nested class** `IntegerCache`. 'Static nested class' means it belongs to `Integer` itself, not to any instance, and 'private' means only `Integer` can use it. It holds: - a `static final int low = -128;` - a `static final int high;` (computed at load time) - a `static final Integer[] cache;` — an array of pre-constructed `Integer` objects, one per value from `low` to `high` inclusive. When the `Integer` class is loaded, a **static initializer** runs once and fills the array: `cache[k]` holds an `Integer` for the value `low + k`. These are the **Flyweight** instances — built eagerly, shared for the life of the process. ## How valueOf uses it The logic of `Integer.valueOf(int i)` is essentially: ```java if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i - IntegerCache.low]; // shared instance return new Integer(i); // fresh object ``` The offset `i - low` (i.e. `i + 128`) maps the value to its array slot. Inside the range you always get the same object; outside it you always get a new one. Because autoboxing compiles to `valueOf`, this is the universal path for boxing literals and assignments. ## What is fixed vs configurable - **Low bound (-128): fixed.** The JLS requires `valueOf` to cache at least -128..127, and the implementation hard-codes `low = -128`. You cannot move it. - **High bound: configurable upward.** It defaults to 127 but can be raised with the JVM option `-XX:AutoBoxCacheMax=<N>` (HotSpot) or the system property `java.lang.Integer.IntegerCache.high`. The implementation takes `max(127, requested)`, so you can only **extend** the cache, never shrink it below 127. Raising it caches more values (e.g. up to 1000), trading a bit of startup memory for fewer allocations of those values. ## Other wrapper caches - `Long`, `Short`, `Byte`: cache **-128..127**, **not configurable**. - `Character`: caches **0..127**. - `Boolean`: `TRUE`/`FALSE` are effectively the only two instances. - `Float`, `Double`: **no cache** — every box is a new object (caching them is pointless since float identity rarely helps and equal float values are common but uniform sharing buys little). ## Why the high bound is the only safe lever — and even then, sparingly The cache exists purely for **memory/allocation efficiency**: real programs box the same small integers constantly. Raising `AutoBoxCacheMax` is a legitimate (rare) tuning move when profiling shows heavy boxing of values just above 127. It is **never** a correctness tool: code must not depend on `==` returning true because a value happens to be cached, because that ties program behavior to a JVM flag and breaks the moment the flag, the value range, or the JVM changes. Correctness for value comparison is always `equals()` / `Objects.equals()`. ## Practical senior takeaways - The flyweights are real, shared, immutable objects created at class init — so `Integer.valueOf` is allocation-free in range. - Know the asymmetry: low fixed, high raisable, and only for `Integer`. - Treat `AutoBoxCacheMax` as a niche performance knob, audited, not a behavioral dependency.

  • If you set -XX:AutoBoxCacheMax=1000, does Integer a = 500; b = 500; a == b become true?
    Yes, because 500 is now in the cache and both refer to the same flyweight. But relying on this is an anti-pattern — it makes correctness depend on a JVM flag; still use equals().
  • Why doesn't the JDK cache Double values like it caches Integer?
    Doubles span a vast, continuous range with little repetition of identical bit patterns, so a small fixed cache would rarely hit; the memory/lookup cost isn't justified, and NaN/+-0.0 identity semantics complicate sharing.

saying these in an interview costs you the question

  • Claiming you can lower the cache below 127
  • Saying Long/Short caches are configurable like Integer's
  • Using AutoBoxCacheMax to make == 'work'
  • Believing Float/Double are cached

context