TL;DR
Redis throws ERR value is not a valid float when INCRBYFLOAT hits a key that can't be parsed as a number โ think empty strings, "1,5" with a comma decimal, or "NaN". The fix: make sure the key holds a clean numeric string before calling INCRBYFLOAT.
What triggers this error
Try this and you'll see it instantly:
127.0.0.1:6379> SET price "not-a-number"
OK
127.0.0.1:6379> INCRBYFLOAT price 1.5
(error) ERR value is not a valid float
Empty strings and locale-formatted numbers hit the same wall:
127.0.0.1:6379> SET counter ""
OK
127.0.0.1:6379> INCRBYFLOAT counter 0.1
(error) ERR value is not a valid float
127.0.0.1:6379> SET ratio "1,75" # comma decimal โ invalid in Redis
OK
127.0.0.1:6379> INCRBYFLOAT ratio 0.25
(error) ERR value is not a valid float
Under the hood, Redis calls strtod() to parse the stored value. That function accepts the same format as a C double literal: optional sign, digits, dot, exponent โ so 1.5, -3e2, and .75 all pass. Everything else gets rejected.
Four ways this sneaks into production
Most teams hit this error through one of these paths:
- Formatted strings from the app layer. A Python or PHP app called
SET price "1,500.00"with thousand-separator commas, or stored a currency string like"$9.99". - Empty-string placeholder. Someone wrote
SET counter ""to "initialize" the key instead ofSET counter 0. - Wrong data type leaked in. A boolean (
"true"), a JSON blob ("{\"v\":1}"), or a shell-echoed value with a trailing newline. - Serialized collection stored as a string. A value like
"[1,2,3]"is a valid string type (won't give youWRONGTYPE), but it's still not a parseable float.
Fix 1 โ Check what's actually stored
Don't guess. Look first:
127.0.0.1:6379> TYPE mykey
string
127.0.0.1:6379> GET mykey
"not-a-number"
If TYPE returns anything other than string, you've got a wrong-type problem โ not a float-format one. The rest of the fixes below apply only to string keys.
Fix 2 โ Overwrite the bad value
Simplest fix: just replace it with a real number.
# Reset to zero
127.0.0.1:6379> SET price 0
OK
127.0.0.1:6379> INCRBYFLOAT price 1.5
"1.5"
# Or start from a specific value
127.0.0.1:6379> SET price 9.99
OK
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"
Fix 3 โ Initialize safely with SETNX
Multiple workers racing to create the same key? Use SET โฆ NX so you don't clobber a valid value that's already there:
127.0.0.1:6379> SET price 0 NX
OK # only sets if key does not exist
127.0.0.1:6379> INCRBYFLOAT price 2.50
"2.5"
It's a no-op if the key already holds a good value. This is the pattern most Redis clients use in high-concurrency counters.
Fix 4 โ Validate before writing from application code
The real fix is upstream: catch bad values before they ever reach Redis.
Python (redis-py)
import redis
r = redis.Redis()
def safe_incrbyfloat(key: str, increment: float) -> float:
current = r.get(key)
if current is not None:
try:
float(current) # raises ValueError if stored value is junk
except ValueError:
r.set(key, 0) # reset before incrementing
return r.incrbyfloat(key, increment)
result = safe_incrbyfloat("price", 1.5)
print(result) # 1.5
Node.js (ioredis)
const Redis = require('ioredis');
const client = new Redis();
async function safeIncrByFloat(key, increment) {
const current = await client.get(key);
if (current !== null && isNaN(parseFloat(current))) {
await client.set(key, 0);
}
return client.incrbyfloat(key, increment);
}
const result = await safeIncrByFloat('price', 1.5);
console.log(result); // '1.5'
Fix 5 โ Atomic check-and-increment with Lua
The Python and Node.js examples above have a race window between GET and INCRBYFLOAT. If that matters, move the logic into a Lua script โ Redis executes it atomically:
local val = redis.call('GET', KEYS[1])
if val == false or tonumber(val) == nil then
redis.call('SET', KEYS[1], 0)
end
return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])
Run it directly:
redis-cli EVAL "
local val = redis.call('GET', KEYS[1])
if val == false or tonumber(val) == nil then
redis.call('SET', KEYS[1], 0)
end
return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])
" 1 price 1.5
Edge cases worth knowing
- Trailing newline from shell echo.
redis-cli SET key $(echo "1.5")stores"1.5\n"โ Redis rejects it. Useprintfor quote the value explicitly. - Locale-formatted numbers. Neither
"1.234,56"(European) nor"1,234.56"(US with thousands separator) will parse. Strip formatting before storing. - NaN and Infinity. Both are valid IEEE 754 floats but Redis rejects them explicitly. Only finite numeric strings pass
strtod(). - The increment itself. The second argument to
INCRBYFLOATmust also be a valid float โ though most client libraries catch this before the command hits Redis.
Verification steps
After applying a fix, run this sequence to confirm everything works:
# 1. Confirm the stored value is numeric
127.0.0.1:6379> GET price
"0"
# 2. INCRBYFLOAT should return a number, not an error
127.0.0.1:6379> INCRBYFLOAT price 9.99
"9.99"
# 3. Increment again to verify accumulation
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"
# 4. GET the final value to be sure
127.0.0.1:6379> GET price
"10"
Debugging in production and can't touch the value? Use this non-destructive probe:
# INCRBYFLOAT with 0 still validates the stored value without changing it
127.0.0.1:6379> INCRBYFLOAT suspicious_key 0
# error โ stored value is not a valid float
# no error โ stored value is fine
Quick reference
- Valid values:
"0","1.5","-3.14","2e3",".5" - Invalid values:
"","abc","1,5","$9.99","NaN","Infinity","true" - Safe init pattern:
SET key 0 NXbefore the firstINCRBYFLOAT - Need atomicity: wrap init + increment in a Lua script

