State Management Example Bot
In this example section, we will create a bot that demonstrates Telegram's state management system. The bot will guide users through a multi-step conversation to collect their name and age, using states to maintain context between interactions.
We will use the following handlers:
- onMessagewith- filter.commands()or- @MessageHandlerwith- commandparameter for command triggers
- onMessagewith- filter.state()or- @MessageHandlerwith- stateparameter for state-specific message handling
Import necessary classes
package io.github.natanimn;
import io.github.natanimn.telebof.BotClient;
import io.github.natanimn.telebof.BotContext;
import io.github.natanimn.telebof.enums.ParseMode;
import io.github.natanimn.telebof.enums.MessageType;
import io.github.natanimn.telebof.annotations.MessageHandler;
import io.github.natanimn.telebof.types.updates.Message;
Create StateExampleBot class and initialize BotClient with state handlers
public class StateExampleBot {
    BotClient bot;
    public StateExampleBot(String token){
        bot = new BotClient(token);
        // Register handlers for different states and commands
        bot.onMessage(filter -> filter.commands("start"), this::start);
        bot.onMessage(filter -> filter.commands("cancel") && filter.state("*"), this::cancel);
        bot.onMessage(filter -> filter.state("name") && filter.text(), this::getName);
        bot.onMessage(filter -> filter.state("age") && filter.text(), this::getAge);
    }
}
Our bot will manage the following conversation flow using states:
- /start command → Sets state to "name"and asks for user's name
- User provides name → Sets state to "age"and asks for age
- User provides age → Processes both pieces of information and clears state
- /cancel command → Clears current state at any point in the conversation
State Handlers Implementation
1. Start Handler - Initiating the Conversation
The /start command begins the multi-step interaction by setting the initial state.
@MessageHandler(commands = "start")
private void start(BotContext ctx, Message message){
    // Ask for the user's name
    ctx.sendMessage(message.from.id, "Hey! What is your name?").exec();
    // Set the user's state to "name" to indicate we're expecting their name next
    ctx.setState(message.from.id, "name");
}
Explanation:
- setState(user_id, "name")marks that this user is now in the "name" state
- The next message from this user will be processed by the getNamehandler
2. Name Handler - Collecting the Name
This handler is triggered when a user in the "name" state sends any message.
private void getName(BotContext ctx, Message message){
    // Ask for the user's age
    ctx.sendMessage(message.from.id, "How old are you?").exec();
    // Transition to the "age" state
    ctx.setState(message.from.id, "age");
    // Store the provided name in state data for later use
    var data = ctx.getStateData(message.from.id);
    data.put("name", message.text);
}
- filter.state("name")ensures this handler only processes messages from users in the "name" state
- filter.text()ensures that user only enter- text
- getStateData(user_id)retrieves a Map where we can store temporary conversation data
- data.put("name", message.text)saves the user's name for the final summary
@MessageHandler(type = MessageType.TEXT, state = "name")
private void getName(BotContext ctx, Message message){
    // Ask for the user's age
    ctx.sendMessage(message.from.id, "How old are you?").exec();
    // Transition to the "age" state
    ctx.setState(message.from.id, "age");
    // Store the provided name in state data for later use
    var data = ctx.getStateData(message.from.id);
    data.put("name", message.text);
}
Key Concepts:
- state = "name"ensures this handler only processes messages from users in the "name" state
- type = MessageType.TEXTensures that user only enter- text
- getStateData(user_id)retrieves a Map where we can store temporary conversation data
- data.put("name", message.text)saves the user's name for the final summary
3. Age Handler - Collecting and Processing Age
This handler processes messages from users in the "age" state.
private void getAge(BotContext ctx, Message message){
    int age;
    // Validate that the input is a number
    try {
        age = Integer.parseInt(message.text);
    } catch (NumberFormatException e) {
        ctx.sendMessage(message.from.id, "Please enter a valid number").exec();
        return; // Stay in the "age" state until valid input is provided
    }
    // Retrieve the stored name from state data
    var data = ctx.getStateData(message.from.id);
    // Send confirmation message
    ctx.sendMessage(message.from.id, "Thank you for the information you have provided.").exec();
    // Display the collected information
    ctx.sendMessage(message.from.id, 
            String.format("<b>Name:</b> %s\n<b>Age:</b> %d", data.get("name"), age))
            .parseMode(ParseMode.HTML)
            .exec();
    // The state is automatically cleared after the conversation completes
    ctx.clearState(message.from.id);
}
@MessageHandler(state = "age", type = MessageType.TEXT)
private void getAge(BotContext ctx, Message message){
    int age;
    // Validate that the input is a number
    try {
        age = Integer.parseInt(message.text);
    } catch (NumberFormatException e) {
        ctx.sendMessage(message.from.id, "Please enter a valid number").exec();
        return; // Stay in the "age" state until valid input is provided
    }
    // Retrieve the stored name from state data
    var data = ctx.getStateData(message.from.id);
    // Send confirmation message
    ctx.sendMessage(message.from.id, "Thank you for the information you have provided.").exec();
    // Display the collected information
    ctx.sendMessage(message.from.id, 
            String.format("<b>Name:</b> %s\n<b>Age:</b> %d", data.get("name"), age))
            .parseMode(ParseMode.HTML)
            .exec();
    // The state is automatically cleared after the conversation completes
    ctx.clearState(message.from.id);
}
Explanation:
- Input validation ensures we get a valid age before proceeding
- data.get("name")retrieves the name stored in the previous step
- The state is automatically handled by the framework after completion
4. Cancel Handler - Aborting the Conversation
The /cancel command can be used at any point to abort the current conversation.
private void cancel(BotContext ctx, Message message){
    // Clear the user's current state and any stored data
    ctx.clearState(message.from.id);
    ctx.sendMessage(message.from.id, 
            "Your information has been cleared. Type /start to begin again.").exec();
}
Key Feature:
- filter.state("*")matches users in any active state, allowing cancellation from any step
- clearState(user_id)removes both the state marker and any associated data
@MessageHandler(commands = "cancel", state = "*", priority = -1)
private void cancel(BotContext ctx, Message message){
    // Clear the user's current state and any stored data
    ctx.clearState(message.from.id);
        ctx.sendMessage(message.from.id, 
        "Your information has been cleared. Type /start to begin again."
    ).exec();
}
- state = "*"matches users in any active state, allowing cancellation from any step
- priority = -1registered and executed before all handlers
- clearState(user_id)removes both the state marker and any associated data

Running the Bot
public void run(){
    bot.startPolling();
}
public static void main(String[] args){
    var token = System.getenv("TOKEN"); // Get token from environment variable
    var state_bot = new StateExampleBot(token);
    state_bot.run();
}
Conversation Flow Example

- User sends /start→ Bot sets state to"name"and asks for name
- User provides name → Bot stores name, sets state to "age", asks for age
- User provides age → Bot displays collected information
- (Optional) User can send /cancelat any time to abort the process
The full source code can be found on examples/state_bot