When your bot grows to thousands of servers, you need sharding to distribute the load across multiple connections. This guide explains how to implement sharding with JDA.
What is Sharding?
Sharding splits your bot’s guilds across multiple WebSocket connections (shards). Each shard handles a subset of your guilds, allowing you to:
- Scale beyond Discord’s per-connection limits
- Distribute processing load
- Improve reliability and performance
- Meet Discord’s sharding requirements
Discord requires sharding when your bot reaches 2,500 guilds. You can implement it earlier for better performance.
When to Shard
You should implement sharding when:
- Your bot is in 2,500+ guilds (required by Discord)
- You want to distribute load across multiple processes
- You need better isolation between guild groups
- You’re preparing for future growth
ShardManager vs Manual Sharding
JDA offers two approaches:
DefaultShardManager (Recommended)
Manages multiple shards automatically in one process:
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder;
import net.dv8tion.jda.api.requests.GatewayIntent;
import java.util.EnumSet;
EnumSet<GatewayIntent> intents = EnumSet.of(
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.MESSAGE_CONTENT
);
DefaultShardManagerBuilder builder = DefaultShardManagerBuilder
.createDefault(token)
.setEnabledIntents(intents)
.addEventListeners(new MyEventListener());
ShardManager shardManager = builder.build();
Manual Sharding
Run separate JDA instances across multiple processes:
// Process 1: Shard 0
JDA shard0 = JDABuilder.createDefault(token)
.useSharding(0, 2) // Shard 0 of 2
.build();
// Process 2: Shard 1
JDA shard1 = JDABuilder.createDefault(token)
.useSharding(1, 2) // Shard 1 of 2
.build();
Using DefaultShardManager
Basic Setup
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder;
import net.dv8tion.jda.api.sharding.ShardManager;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.entities.Activity;
import java.util.EnumSet;
public class ShardedBot {
public static void main(String[] args) {
String token = System.getenv("BOT_TOKEN");
EnumSet<GatewayIntent> intents = EnumSet.of(
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.DIRECT_MESSAGES,
GatewayIntent.MESSAGE_CONTENT
);
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.setEnabledIntents(intents)
.addEventListeners(new CommandHandler())
.setActivity(Activity.playing("on multiple shards"))
.build();
System.out.println("Shard Manager started!");
}
}
Builder Presets
Like JDABuilder, DefaultShardManagerBuilder offers presets:
// Minimal cache (low memory)
ShardManager sm = DefaultShardManagerBuilder
.createLight(token, intents)
.build();
// Recommended defaults
ShardManager sm = DefaultShardManagerBuilder
.createDefault(token)
.build();
// Full cache
ShardManager sm = DefaultShardManagerBuilder
.create(token, intents)
.build();
Configuring Shard Count
Automatic (Recommended)
Let Discord determine the optimal shard count:
// Discord automatically calculates shard count
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.build();
Manual Shard Count
Specify the total number of shards:
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.setShardsTotal(4) // Create 4 shards
.build();
Specific Shard IDs
Run only certain shards (useful for distributed systems):
import java.util.List;
// Only run shards 0, 1, and 2 out of 10 total
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.setShardsTotal(10)
.setShards(0, 1, 2) // Run these specific shards
.build();
// Or use a collection
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.setShardsTotal(10)
.setShards(List.of(0, 1, 2))
.build();
Per-Shard Configuration
Per-Shard Activities
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.setActivityProvider(shardId ->
Activity.playing("Shard " + shardId)
)
.build();
Per-Shard Status
import net.dv8tion.jda.api.OnlineStatus;
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.setStatusProvider(shardId -> {
// Shard 0 is always online, others idle
return shardId == 0 ? OnlineStatus.ONLINE : OnlineStatus.IDLE;
})
.build();
Per-Shard Event Listeners
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.addEventListenerProvider(shardId -> new ShardSpecificListener(shardId))
.build();
public class ShardSpecificListener extends ListenerAdapter {
private final int shardId;
public ShardSpecificListener(int shardId) {
this.shardId = shardId;
}
@Override
public void onMessageReceived(MessageReceivedEvent event) {
System.out.println("Shard " + shardId + " received: " +
event.getMessage().getContentDisplay());
}
}
Working with Shards
Accessing Individual Shards
ShardManager shardManager = // ... build
// Get a specific shard
JDA shard = shardManager.getShardById(0);
// Get shard for a specific guild
Guild guild = // ...
JDA shardForGuild = shardManager.getShardById(guild.getIdLong());
// Iterate all shards
for (JDA shard : shardManager.getShards()) {
System.out.println("Shard " + shard.getShardInfo().getShardId() +
" has " + shard.getGuilds().size() + " guilds");
}
Shard Statistics
ShardManager shardManager = // ... build
// Total guilds across all shards
long totalGuilds = shardManager.getGuildCache().size();
// Total users across all shards
long totalUsers = shardManager.getUserCache().size();
// Shards information
int totalShards = shardManager.getShardsTotal();
int runningShards = shardManager.getShardsRunning();
System.out.println("Running " + runningShards + "/" + totalShards + " shards");
System.out.println("Total guilds: " + totalGuilds);
Checking Shard Status
for (JDA shard : shardManager.getShards()) {
JDA.Status status = shard.getStatus();
int shardId = shard.getShardInfo().getShardId();
System.out.println("Shard " + shardId + ": " + status);
// Check if ready
if (status == JDA.Status.CONNECTED) {
System.out.println(" Guilds: " + shard.getGuilds().size());
}
}
Session Controller
Control how shards connect to Discord:
import net.dv8tion.jda.api.utils.SessionController;
import net.dv8tion.jda.api.utils.SessionControllerAdapter;
// Custom session controller
SessionController controller = new SessionControllerAdapter() {
@Override
public void appendSession(SessionConnectNode node) {
// Custom connection logic
super.appendSession(node);
}
};
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
.setSessionController(controller)
.build();
The default session controller handles rate limiting and sequential shard startup automatically.
Thread Pool Configuration
Configure thread pools for optimal performance:
import net.dv8tion.jda.api.sharding.ThreadPoolProvider;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
// Gateway pool (WebSocket handling)
.setGatewayPoolProvider(total ->
Executors.newScheduledThreadPool(
Math.max(1, (int) Math.log(total))
)
)
// Callback pool (RestAction callbacks)
.setCallbackPoolProvider(total ->
Executors.newFixedThreadPool(total * 2)
)
// Event pool (event dispatching)
.setEventPoolProvider(total ->
Executors.newFixedThreadPool(total)
)
.build();
Cache Configuration
Same cache flags work with ShardManager:
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
ShardManager shardManager = DefaultShardManagerBuilder
.createDefault(token)
// Disable specific cache flags
.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS)
// Set member cache policy
.setMemberCachePolicy(MemberCachePolicy.VOICE)
.build();
Shard-Aware Commands
Register commands across all shards:
import net.dv8tion.jda.api.interactions.commands.build.Commands;
// Register globally across all shards
shardManager.getShards().forEach(shard -> {
shard.updateCommands()
.addCommands(
Commands.slash("stats", "Show shard statistics")
)
.queue();
});
// Or use ShardManager directly
shardManager.getShardCache().forEach(shard -> {
// Configure each shard
});
Handle shard information in commands:
@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
if (event.getName().equals("stats")) {
JDA jda = event.getJDA();
JDA.ShardInfo shardInfo = jda.getShardInfo();
String response = String.format(
"Shard %d/%d\nGuilds: %d\nPing: %dms",
shardInfo.getShardId(),
shardInfo.getShardTotal(),
jda.getGuilds().size(),
jda.getGatewayPing()
);
event.reply(response).setEphemeral(true).queue();
}
}
Shutdown and Restart
Graceful Shutdown
// Shutdown all shards
shardManager.shutdown();
// Shutdown specific shard
shardManager.getShardById(0).shutdown();
// Shutdown and wait
shardManager.shutdown();
shardManager.awaitShutdown(); // Blocks until complete
Restart Shard
// Restart a specific shard
JDA shard = shardManager.getShardById(0);
shard.shutdown();
shardManager.restart(0);
Best Practices
Sharding Tips:
- Start with automatic shard count (let Discord decide)
- Use
createDefault() or createLight() presets
- Monitor shard health and restart problematic shards
- Use per-shard listeners for shard-specific behavior
- Consider manual sharding across processes for very large bots (10,000+ guilds)
Common Issues:
- Don’t hardcode shard counts - let Discord calculate or use manual override only when needed
- Always handle events from all shards when using ShardManager
- Be aware that cache is per-shard, not global
- Rate limits are applied per-shard
Monitoring Shards
Create a monitoring system:
import net.dv8tion.jda.api.JDA;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ShardMonitor {
private final ShardManager shardManager;
private final ScheduledExecutorService scheduler;
public ShardMonitor(ShardManager shardManager) {
this.shardManager = shardManager;
this.scheduler = Executors.newScheduledThreadPool(1);
}
public void start() {
scheduler.scheduleAtFixedRate(() -> {
for (JDA shard : shardManager.getShards()) {
JDA.ShardInfo info = shard.getShardInfo();
JDA.Status status = shard.getStatus();
long ping = shard.getGatewayPing();
System.out.printf(
"Shard %d/%d - Status: %s, Ping: %dms, Guilds: %d%n",
info.getShardId(),
info.getShardTotal(),
status,
ping,
shard.getGuilds().size()
);
// Restart if shard is stuck
if (status == JDA.Status.DISCONNECTED) {
System.out.println("Restarting shard " + info.getShardId());
shardManager.restart(info.getShardId());
}
}
}, 0, 5, TimeUnit.MINUTES);
}
public void stop() {
scheduler.shutdown();
}
}
Next Steps