FasterXML/jackson-databind · error · IllegalArgumentException

Cannot set maxDeserializerCacheSize to a negative value

Error message

Cannot set maxDeserializerCacheSize to a negative value

What it means

DefaultCacheProvider.Builder.maxDeserializerCacheSize(int) sets the maximum number of entries the deserializer cache (DeserializerCache) may hold; zero disables caching but negative values are nonsensical, so the builder rejects them with IllegalArgumentException. The cache stores resolved ValueDeserializers keyed by type, so a negative cap would mean 'hold fewer than nothing', which is an internal-logic error rather than a tuning choice.

Source

Thrown at src/main/java/tools/jackson/databind/cfg/DefaultCacheProvider.java:166

         * Corresponds to {@link DefaultCacheProvider#_maxTypeFactoryCacheSize}.
         */
        private int _maxTypeFactoryCacheSize = TypeFactory.DEFAULT_MAX_CACHE_SIZE;

        Builder() { }

        /**
         * Define the maximum size of the {@link LookupCache} instance constructed by {@link #forDeserializerCache(DeserializationConfig)}
         * and {@link #_buildCache(int)}.
         * <p>
         * Note that specifying a maximum size of zero prevents values from being retained in the cache.
         *
         * @param maxDeserializerCacheSize Size for the {@link LookupCache} to use within {@link DeserializerCache}
         * @return this builder
         * @throws IllegalArgumentException if {@code maxDeserializerCacheSize} is negative
         */
        public Builder maxDeserializerCacheSize(int maxDeserializerCacheSize) {
            if (maxDeserializerCacheSize < 0) {
                throw new IllegalArgumentException("Cannot set maxDeserializerCacheSize to a negative value");
            }
            _maxDeserializerCacheSize = maxDeserializerCacheSize;
            return this;
        }

        /**
         * Define the maximum size of the {@link LookupCache} instance constructed by {@link #forSerializerCache(SerializationConfig)}
         * and {@link #_buildCache(int)}
         * <p>
         * Note that specifying a maximum size of zero prevents values from being retained in the cache.
         *
         * @param maxSerializerCacheSize Size for the {@link LookupCache} to use within {@link SerializerCache}
         * @return this builder
         * @throws IllegalArgumentException if {@code maxSerializerCacheSize} is negative
         */
        public Builder maxSerializerCacheSize(int maxSerializerCacheSize) {
            if (maxSerializerCacheSize < 0) {
                throw new IllegalArgumentException("Cannot set maxSerializerCacheSize to a negative value");

View on GitHub (pinned to a50c7d2a1d)

Solutions

  1. Use 0 to disable the cache, or a large positive int (e.g. Integer.MAX_VALUE or a documented high bound) for 'effectively unlimited'.
  2. Validate the configured value at the configuration boundary: if (v < 0) throw new IllegalStateException(...).
  3. Map any '-1 means unlimited' convention at the property-reading layer to a concrete large positive value before calling the builder.
  4. Add a unit test asserting the builder rejects negatives and accepts 0 and large positives.

Example fix

// before
int cap = props.getInt("deser.cache.max", -1); // -1 meant 'unlimited' in old lib
DefaultCacheProvider provider = DefaultCacheProvider.builder()
    .maxDeserializerCacheSize(cap).build(); // throws
// after
int cap = props.getInt("deser.cache.max", -1);
int safe = (cap < 0) ? Integer.MAX_VALUE : cap;
DefaultCacheProvider provider = DefaultCacheProvider.builder()
    .maxDeserializerCacheSize(safe).build();
Defensive patterns

Strategy: validation

Validate before calling

int cap = configuredDeserCache;
if (cap < 0) throw new IllegalArgumentException("deser cache < 0: " + cap);
DefaultCacheProvider.builder().maxDeserializerCacheSize(cap).build();

Type guard

// primitive int range check

Try / catch

try {
    return DefaultCacheProvider.builder().maxDeserializerCacheSize(cap).build();
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("negative")) {
        return DefaultCacheProvider.builder().maxDeserializerCacheSize(0).build();
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling builder.maxDeserializerCacheSize(-1) (or any negative); computing the size from a property/expression that can go negative; passing -1 intending 'unlimited' (this builder has no unlimited sentinel — use a very large int or the default).

Common situations: A 'max cache' config where -1 was used to mean 'no limit' in another library but here means invalid; arithmetic (base - overhead) that underflows for small inputs; environment variable parsed with a default of -1 when unset.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@a50c7d2a1d (2026-08-06). Data as JSON: /api/errors/6e05e72e6dd2bdac. Report an issue: GitHub.