valkey_client
A modern, production-ready Dart client for Valkey (9.0.0+). Fully Redis 7.x compatible.
👉 To browse Redis/Valkey data, you can use Keyscope,
a native Redis/Valkey IDE to edit key-value data in your databases.
👉 To access Redis/Valkey servers on Kubernetes, you can use Visualkube Jet,
a native Kubernetes IDE for multi‑cluster access and real-time watch.
Keyscope and Visualkube Jet are plugins for Android Studio and JetBrains IDEs.
The Goal 🎯
The Dart ecosystem needs a high-performance, actively maintained client for the next generation of in-memory databases. This package aims to be the standard Dart client for Valkey (9.0.0+) while maintaining full compatibility with Redis (7.x+).
It is designed primarily for server-side Dart applications (server.dart) requiring a robust and fast connection to Valkey.
We also strive to maintain zero external dependencies. This is a core part of our design philosophy. It prevents dependency conflicts and ensures that valkey_client will never block your project's ability to upgrade its other packages.
Features
- Automatic Failover (v1.8.0+): The client now survives node failures. If a master node goes down (connection refused/timeout), the client automatically refreshes the cluster topology and reroutes commands to the new master without throwing an exception.
- Connection Pool Hardening (v1.7.0+): Implemented Smart Release mechanism. The pool automatically detects and discards "dirty" connections (e.g., inside Transaction or Pub/Sub) upon release, preventing pool pollution and resource leaks.
- Enhanced Developer Experience (v1.7.0+): Expanded
Redisaliases to include Exceptions, Configuration, and Data Models (RedisException,RedisMessage, etc.) for a seamless migration experience. - Sharded Pub/Sub & Atomic Counters (v1.6.0+): Added support for high-performance cluster messaging (
SPUBLISH/SSUBSCRIBE) and atomic integer operations (INCR/DECR). - Developer Experience (v1.5.0+): Added
RedisClientalias and smart redirection handling for better usability and stability. - High Availability & Resilience (v1.5.0+): Automatically and transparently handles cluster topology changes (
-MOVEDand-ASKredirections) to ensure robust failover, seamless scaling, and zero‑downtime operations. - Multi-key Support (v1.4.0+): Supports
MGETacross multiple nodes using smart Scatter-Gather pipelining. - Cluster Client (v1.3.0+): Added
ValkeyClusterClientfor automatic command routing in cluster mode.- This client automatically routes commands to the correct node.
- We recommend using
ValkeyClientfor Standalone/Sentinel andValkeyClusterClientfor cluster environments.
- Built-in Connection Pooling (v1.1.0+):
ValkeyPoolfor efficient connection management (used by Standalone and Cluster clients). - Cluster Auto-Discovery (v1.2.0+): Added
client.clusterSlots()to fetch cluster topology (via theCLUSTER SLOTScommand), laying the foundation for full cluster support. - Command Timeout (v1.2.0+): Includes a built-in command timeout (via
ValkeyConnectionSettings) to prevent client hangs on non-responsive servers. - Robust Parsing: Full RESP3 parser handling all core data types (
+,-,$,*,:). - Type-Safe Exceptions: Clear distinction between connection errors (
ValkeyConnectionException), server errors (ValkeyServerException), and client errors (ValkeyClientException). - Pub/Sub Ready (Standalone/Sentinel):
subscribe()returns aSubscriptionobject with aStreamand aFuture<void> readyfor easy and reliable message handling. - Production-Ready (Standalone/Sentinel):
v1.0.0is stable for production use in non-clustered environments (when used with a connection pool). This lays the foundation for the full cluster support planned for v2.0.0 (see Roadmap).
Command Support
- Connection (
PING,ECHO,QUITviaclose()) - Cluster (
CLUSTER SLOTS,ASKING) - Strings (
GET,SET,MGET,INCR,DECR,INCRBY,DECRBY) - Hashes (
HSET,HGET,HGETALL) - Lists (
LPUSH,RPUSH,LPOP,RPOP,LRANGE) - Sets (
SADD,SREM,SMEMBERS) - Sorted Sets (
ZADD,ZREM,ZRANGE) - Key Management (
DEL,EXISTS,EXPIRE,TTL) - Transactions (
MULTI,EXEC,DISCARD) - Full Pub/Sub (
PUBLISH,SUBSCRIBE,UNSUBSCRIBE,PSUBSCRIBE,PUNSUBSCRIBE) - Pub/Sub Introspection (
PUBSUB CHANNELS,PUBSUB NUMSUB,PUBSUB NUMPAT) - Sharded Pub/Sub (
SPUBLISH,SSUBSCRIBE,SUNSUBSCRIBE) for efficient cluster messaging.
Getting Started
This section describe how to install a Valkey or Redis Server on your local environment.
Kubernetes: Local Setup
- Run Rancher Desktop to set up a local
Kubernetescluster. - In Android Studio or a JetBrains IDE:
- Go to Plugin > Visualkube Jet > Cluster Manager
- Connect one or more
Kubernetesclusters - Navigate to Helm > Charts
- Search for
redis,redis-cluster,valkey, orvalkey-cluster - Install and choose a target cluster
- In Android Studio or a JetBrains IDE:
- Go to Plugin > Keyscope > Cluster Manager
- Add a connection and choose Standalone, Cluster, or Sentinel mode
- Double‑click your
myRedisormyValkeyserver - Double‑click Database
- Double‑click a Key type
- Click keys to view their values
Docker: Local Standalone Setup
This client requires a running Valkey or Redis server to connect to. For local development and testing, we strongly recommend using Docker.
- Install a container environment like Docker Desktop.
- Start a Valkey or Redis server instance by running one of the following commands in your terminal:
Option 1: No Authentication (Default)
# Valkey (latest, e.g., 9.0.0)
docker run -d --name my-valkey -p 6379:6379 valkey/valkey:latest
# Redis (latest, e.g., 8.2.3)
docker run -d --name my-redis -p 6379:6379 redis:latest
Option 2: With Password Only
(This sets the password for the default user. Use with username: null in the client.)
# Valkey (latest, e.g., 9.0.0)
docker run -d --name my-valkey-auth -p 6379:6379 valkey/valkey:latest \
--requirepass "my-super-secret-password"
# Redis (latest, e.g., 8.2.3)
docker run -d --name my-redis-auth -p 6379:6379 redis:latest \
--requirepass "my-super-secret-password"
Option 3: With Username and Password (ACL)
(This sets the password for the default user. Use with username: 'default' in the client.)
# Valkey (latest, e.g., 9.0.0)
docker run -d --name my-valkey-acl -p 6379:6379 valkey/valkey:latest \
--user default --pass "my-super-secret-password"
# Redis (latest, e.g., 8.2.3)
docker run -d --name my-redis-acl -p 6379:6379 redis:latest \
--user default --pass "my-super-secret-password"
- Valkey/Redis 6+ uses ACLs. The
defaultuser exists by default. To create a new user instead, simply change--user defaultto--user my-user.
(Note: The '-d' flag runs the container in "detached" mode (in the background). You can remove it if you want to see the server logs directly in your terminal.)
Docker Compose: Local Cluster Setup (for v1.3.0+ Testing)
The Usage (Group 3) examples require a running Valkey Cluster or Redis Cluster.
Setting up a cluster on Docker Desktop (macOS/Windows) is notoriously complex due to the networking required for NAT (mapping internal container IPs to 127.0.0.1).
To solve this and encourage contributions, the GitHub repository provides a pre-configured, one-command setup file.
File Location:
Each provided YAML file is a Docker Compose configuration that launches a 6-node (3 Master, 3 Replica) cluster. It is already configured to handle all IP announcement (e.g., --cluster-announce-ip) and networking challenges automatically.
How to Run the Cluster:
- Download the
valkey_macos.yamlorredis_macos.yamlfile from the repository. - In your terminal, navigate to the file's location.
- To start the cluster, run one of the following commands:
# To start Valkey Cluster docker compose -f valkey_macos.yaml up --force-recreate # To start Redis Cluster docker compose -f redis_macos.yaml up --force-recreate - Wait for the
cluster-initservice to logâś… Cluster is stable and all slots are covered!.
Your 6-node cluster is now running on 127.0.0.1:7001-7006, and you can successfully run the Usage (Group 3) examples.
Note: This configuration starts from port 7001 (instead of the common 7000) because port 7000 is often reserved by the macOS Control Center (AirPlay Receiver) service.
Developer Experience Improvements (v1.7.0+)
To enhance DX for both Redis and Valkey developers, we provide fully compatible aliases. You can use the class names you are most comfortable with.
Clients
| Role | Redis Alias | Valkey Class | Description |
|---|---|---|---|
| Client | RedisClient |
ValkeyClient |
Standard client for Standalone or Sentinel connections. |
| Cluster | RedisClusterClient |
ValkeyClusterClient |
Auto-routing client for Cluster environments. |
| Pooling | RedisPool |
ValkeyPool |
Manages connection pools for high-concurrency apps. |
Configuration
| Role | Redis Alias | Valkey Class | Description |
|---|---|---|---|
| Settings | RedisConnectionSettings |
ValkeyConnectionSettings |
Configuration for host, port, password, and timeout. |
| Logging | RedisLogLevel |
ValkeyLogLevel |
Logging levels (info, warning, severe, off). |
Data Models
| Role | Redis Alias | Valkey Class | Description |
|---|---|---|---|
| Message | RedisMessage |
ValkeyMessage |
Represents a message received via Pub/Sub. |
Exceptions (Crucial for try-catch blocks)
| Role | Redis Alias | Valkey Class | Description |
|---|---|---|---|
| Base Error | RedisException |
ValkeyException |
The base class for all package-related exceptions. |
| Network | RedisConnectionException |
ValkeyConnectionException |
Thrown when connection fails or drops. |
| Server | RedisServerException |
ValkeyServerException |
Thrown when the server returns an error (e.g., -ERR). |
| Usage | RedisClientException |
ValkeyClientException |
Thrown on invalid client usage (e.g., misuse of API). |
| Parsing | RedisParsingException |
ValkeyParsingException |
Thrown when the response cannot be parsed (RESP3). |
Usage
Redis
RedisClient is available as an alias of ValkeyClient to enhance developer experience (DX).
You can use either ValkeyClient or RedisClient.
Both classes and helper functions are fully compatible — choose whichever name feels natural for your project.
Quick Example (RedisClient)
// Since v1.5.0, you can import RedisClient directly — no need to switch to ValkeyClient.
import 'package:valkey_client/redis_client.dart';
void main() async {
final client = RedisClient();
try {
await client.connect(
host: '127.0.0.1',
port: 6379
);
await client.set('key', 'value');
print(await client.get('key'));
} catch (e) {
print('❌ Failed: $e');
} finally {
await client.close();
}
}
👉 All examples below use ValkeyClient, but you can replace it with RedisClient without any changes.
Note: Not all examples are fully interchangeable yet, but we plan to make them 100% compatible.
Valkey
valkey_client supports Standalone, Sentinel, and Cluster environments.
New users are encouraged to start with Group 1. Production applications should use Group 2 (for Standalone/Sentinel) or Group 3 (for Cluster).
Group 1: Standalone/Sentinel (Single Connection)
This is the most basic way to connect and run commands using the ValkeyClient class. It is recommended for new users, simple tests, and scripts. (See more examples in the Example tab.)
1. Basic: Connection Patterns (from example/valkey_client_example.dart)
ValkeyClient can be configured via its constructor (fixedClient) or by passing settings to the connect() method (flexibleClient).
// 1. "Fixed Client": Constructor-based configuration
// (Match your server setup from the "Getting Started" section)
// Option 1: No Authentication
final fixedClient = ValkeyClient(host: '127.0.0.1', port: 6379);
// Option 2: Password Only
// final fixedClient = ValkeyClient(host: '127.0.0.1', port: 6379, password: 'my-super-secret-password');
// Option 3: Username + Password (ACL)
// final fixedClient = ValkeyClient(host: '127.0.0.1', port: 6379, username: 'default', password: 'my-super-secret-password');
try {
await fixedClient.connect();
print(await fixedClient.ping()); // Output: PONG
} finally {
await fixedClient.close();
}
// 2. "Flexible Client": Method-based configuration
// This pattern is useful for managing connections dynamically.
final flexibleClient = ValkeyClient(); // No config in constructor
try {
await flexibleClient.connect(
host: '127.0.0.1',
port: 6379,
// password: 'my-super-secret-password'
);
print(await flexibleClient.ping()); // Output: PONG
} finally {
await flexibleClient.close();
}
2. Standard: Basic Usage (from example/simple_example.dart)
This is the standard try-catch-finally structure to handle exceptions and ensure close() is always called.
import 'package:valkey_client/valkey_client.dart';
void main() async {
final client = ValkeyClient(host: '127.0.0.1', port: 6379);
try {
await client.connect();
await client.set('greeting', 'Hello, Valkey!');
final value = await client.get('greeting');
print(value); // Output: Hello, Valkey!
} on ValkeyConnectionException catch (e) {
print('Connection failed: $e');
} on ValkeyServerException catch (e) {
print('Server returned an error: $e');
} finally {
// Always close the connection
await client.close();
}
}
Atomic Counters (v1.6.0+)
// Atomic increment/decrement operations
await client.set('score', '10');
final newScore = await client.incr('score'); // 11
await client.decrBy('score', 5); // 6
3. Application: Pub/Sub (from example/valkey_client_example.dart)
ValkeyClient supports Pub/Sub. subscribe() returns a Subscription object, and you must await sub.ready to ensure the subscription is active before publishing.
// Use two clients: one to subscribe, one to publish
final subscriber = ValkeyClient(host: '127.0.0.1', port: 6379);
final publisher = ValkeyClient(host: '127.0.0.1', port: 6379);
StreamSubscription<ValkeyMessage>? listener;
try {
await Future.wait([subscriber.connect(), publisher.connect()]);
final channel = 'news:updates';
// 1. Subscribe and get the Subscription object
final sub = subscriber.subscribe([channel]);
// 2. MUST await sub.ready before publishing
print('Waiting for subscription confirmation...');
await sub.ready.timeout(Duration(seconds: 2));
print('Subscription confirmed!');
// 3. Listen to the message stream
listener = sub.messages.listen((message) {
print('📬 Received: ${message.message} (from channel: ${message.channel})');
});
// 4. Publish messages
await publisher.publish(channel, 'Valkey v1.3.0 has been released!');
await Future.delayed(Duration(seconds: 1)); // Wait to receive message
} catch (e) {
print('❌ Pub/Sub Example Failed: $e');
} finally {
await listener?.cancel();
await Future.wait([subscriber.close(), publisher.close()]);
print('Pub/Sub clients closed.');
}
Group 2: Production Pool (Standalone/Sentinel)
Connection Pooling (v1.1.0+)
For all applications — and especially for production server environments with high concurrency — it is strongly recommended to use the built-in ValkeyPool class instead of managing single ValkeyClient connections or connecting/closing individual clients.
The pool manages connections efficiently, preventing performance issues and resource exhaustion.
See below for both basic and application pooling examples for concurrent requests, including acquiring/releasing connections, handling wait queues, and choosing the right approach for your workload.
1. Basic: Pool Usage (from example/simple_pool_example.dart)
Acquire a connection with pool.acquire() and return it with pool.release().
import 'package:valkey_client/valkey_client.dart';
void main() async {
// 1. Define connection settings for the pool
final settings = ValkeyConnectionSettings(
host: '127.0.0.1',
port: 6379,
// password: 'my-super-secret-password',
);
// 2. Create a pool (e.g., max 10 connections)
final pool = ValkeyPool(connectionSettings: settings, maxConnections: 10);
ValkeyClient? client;
try {
// 3. Acquire a client from the pool
client = await pool.acquire();
// 4. Run commands
await client.set('greeting', 'Hello from ValkeyPool!');
final value = await client.get('greeting');
print(value); // Output: Hello from ValkeyPool!
} on ValkeyConnectionException catch (e) {
print('Connection or pool acquisition failed: $e');
} on ValkeyServerException catch (e) {
print('Server returned an error: $e');
} finally {
// 5. Release the client back to the pool
if (client != null) {
pool.release(client);
}
// 6. Close the pool when the application shuts down
await pool.close();
}
}
2. Application: Concurrent Pool Handling (from example/pool_example.dart)
This shows how ValkeyPool handles concurrent requests up to maxConnections and uses a wait queue when the pool is full.
import 'dart:async';
import 'package:valkey_client/valkey_client.dart';
/// Helper function to simulate a web request using the pool.
Future<void> handleRequest(ValkeyPool pool, String userId) async {
ValkeyClient? client;
try {
// 1. Acquire connection (waits if pool is full)
print('[$userId] Acquiring connection...');
client = await pool.acquire().timeout(Duration(seconds: 2));
print('[$userId] Acquired!');
// 2. Use connection
await client.set('user:$userId', 'data');
await Future.delayed(Duration(milliseconds: 500)); // Simulate work
} on ValkeyException catch (e) {
print('[$userId] Valkey Error: $e');
} on TimeoutException {
print('[$userId] Timed out waiting for a connection!');
} finally {
// 3. Release connection back to pool
if (client != null) {
print('[$userId] Releasing connection...');
pool.release(client);
}
}
}
Future<void> main() async {
final settings = ValkeyConnectionSettings(host: '127.0.0.1', port: 6379);
// Create a pool with a max of 3 connections
final pool = ValkeyPool(connectionSettings: settings, maxConnections: 3);
print('Simulating 5 concurrent requests with a pool size of 3...');
// 3. Simulate 5 concurrent requests
final futures = <Future>[
handleRequest(pool, 'UserA'),
handleRequest(pool, 'UserB'),
handleRequest(pool, 'UserC'), // These 3 get connections immediately
handleRequest(pool, 'UserD'), // This one will wait
handleRequest(pool, 'UserE'), // This one will wait
];
await Future.wait(futures);
await pool.close();
}
Smart Release (v1.7.0+)
ValkeyPool now supports Smart Release. You don't need to manually discard connections that have changed state (e.g., inside a Transaction or Pub/Sub) when releasing it.
// Just use release()!
// The pool automatically detects if the client is dirty (Stateful) or closed
// and efficiently discards/replaces it if necessary.
pool.release(client);
Group 3: Cluster Mode (Advanced)
This group is for connecting to a Valkey Cluster Mode environment.
Sharded Pub/Sub (v1.6.0+)
ValkeyClusterClient for Cluster (from example/cluster_sharded_pubsub_example.dart)
Sharded Pub/Sub reduces network traffic in cluster environments by routing messages only to the node responsible for the channel's slot.
// 1. Subscribe to sharded channels
// In Cluster mode, this automatically routes to the correct nodes.
final sub = client.ssubscribe(['shard:news:{sports}', 'shard:news:{tech}']);
await sub.ready;
// 2. Handle incoming messages
sub.messages.listen((msg) {
print('Received on ${msg.channel}: ${msg.message}');
});
// 3. Publish to a specific shard
await client.spublish('shard:news:{sports}', 'Lakers won!');
// 4. Unsubscribe
await sub.unsubscribe();
Note: You can also use ssubscribe and spublish with ValkeyClient (Standalone) on compatible servers (Redis 7.0+ / Valkey 9.0+). See ValkeyClient for Standalone (from example/sharded_pubsub_example.dart)
Recommended (v1.3.0+): Automatic Routing (from example/cluster_client_example.dart)
Use ValkeyClusterClient. This client auto-discovers the cluster topology via CLUSTER SLOTS on connect() and auto-routes commands like SET/GET to the correct node.
import 'package:valkey_client/valkey_client.dart';
void main() async {
// 1. Define initial seed nodes (only one is needed)
final initialNodes = [
ValkeyConnectionSettings(
host: '127.0.0.1',
port: 7001, // Connect to any node in the cluster
),
];
// 2. Create the cluster client
final client = ValkeyClusterClient(initialNodes);
try {
// 3. Connect (this fetches topology and builds pools)
print('Connecting to cluster...');
await client.connect();
print('âś… Cluster connected and slot map loaded.');
// 4. Run commands (will be auto-routed)
print('\nRunning SET command for "key:A"...');
await client.set('key:A', 'Hello from Node A');
print('Running SET command for "key:B"...');
await client.set('key:B', 'Hello from Node B');
print('GET response for "key:A": ${await client.get("key:A")}');
print('GET response for "key:B": ${await client.get("key:B")}');
} on ValkeyException catch (e) {
print('\n❌ Cluster Error: $e');
} finally {
// 5. Close all pooled cluster connections
print('\nClosing all cluster connections...');
await client.close();
}
}
Automatic Failover (v1.8.0+)
ValkeyClusterClient is resilient to node failures. If a master node crashes or becomes unreachable:
- The client detects the connection failure (e.g.,
SocketException). - It automatically refreshes the cluster topology from remaining nodes.
- It finds the new master node and retries the command.
This happens transparently to the user. You do not need to catch connection exceptions for failovers.
Multi-key Operations (v1.4.0+): Scatter-Gather with Pipelined GETs (from example/cluster_mget_example.dart)
ValkeyClusterClient supports MGET even if keys are distributed across different nodes. It uses a Scatter-Gather strategy with pipelining to ensure high performance and correct ordering.
// Retrieve values from multiple nodes in parallel
final results = await client.mget(['key:A', 'key:B', 'key:C']);
print(results); // ['Value-A', 'Value-B', 'Value-C']
Low-Level (v1.2.0+): Manual Topology Fetch (from example/cluster_auto_discovery_example.dart)
If you need to manually inspect the topology, you can use a standard ValkeyClient (single connection) to call clusterSlots() directly.
import 'package:valkey_client/valkey_client.dart';
void main() async {
// 1. Configure a standard client to connect to ONE node
final client = ValkeyClient(host: '127.0.0.1', port: 7001);
try {
await client.connect();
print('âś… Connected to cluster node at 127.0.0.1:7001');
// 2. Run the v1.2.0 command
print('\nFetching cluster topology using CLUSTER SLOTS...');
final List<ClusterSlotRange> slotRanges = await client.clusterSlots();
// 3. Print the results
print('Cluster topology loaded. Found ${slotRanges.length} slot ranges:');
for (final range in slotRanges) {
print('--------------------');
print(' Slots: ${range.startSlot} - ${range.endSlot}');
print(' Master: ${range.master.host}:${range.master.port}');
}
} on ValkeyException catch (e) {
print('\n❌ Error: $e');
} finally {
await client.close();
}
}
Logging Configuration
By default, the client logs are disabled (ValkeyLogLevel.off).
You can enable logging globally to debug connection or parsing issues.
// Enable detailed logging
ValkeyClient.setLogLevel(ValkeyLogLevel.info);
// Disable logging (default)
ValkeyClient.setLogLevel(ValkeyLogLevel.off);
Planned Features
- Valkey 9.0.0+ Support: Full implementation of the latest commands and features.
- RESP3 Protocol: Built on the modern RESP3 protocol for richer data types and performance.
- High-Performance Async I/O: Non-blocking, asynchronous networking.
- Connection Pooling: Production-grade connection pooling suitable for high-concurrency backend servers.
- Type-Safe & Modern API: A clean, easy-to-use API for Dart developers.
More details in the Roadmap.
Contributing
Your contributions are welcome! Please check the GitHub repository for open issues or submit a Pull Request. For major changes, please open an issue first to discuss the approach.
Maintained By
Maintained by the developers of Visualkube Jet and Keyscope at Infradise Inc. We believe in giving back to the Dart & Flutter community.
License
This project is licensed under the Apache License 2.0.
License Change Notification (2025-10-29)
This project was initially licensed under the MIT License. As of October 29, 2025 (v0.11.0 and later), the project has been re-licensed to the Apache License 2.0.
We chose Apache 2.0 for its robust, clear, and balanced terms, which benefit both users and contributors:
- Contributor Protection (Patent Defense): Includes a defensive patent termination clause. This strongly deters users from filing patent infringement lawsuits against contributors (us).
- User Protection (Patent Grant): Explicitly grants users a patent license for any contributor patents embodied in the code, similar to MIT.
- Trademark Protection (Non-Endorsement): Includes a clause (Section 6) that restricts the use of our trademarks (like
Infradise Inc.orVisualkube), providing an effect similar to the "non-endorsement" clause in the BSD-3 license.
License Compatibility: Please note that the Apache 2.0 license is compatible with GPLv3, but it is not compatible with GPLv2.
All versions published prior to this change remain available under the MIT License. All future contributions and versions will be licensed under Apache License 2.0.
Libraries
- redis_client
- This library provides a Redis-compatible interface.
- valkey_client
- A modern, production-ready Dart client for Valkey (9.0.0+). Fully Redis 7.x compatible.
- valkey_client_base
- valkey_cluster_client_base
- valkey_commands_base
- valkey_pool