eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Introduction

In this tutorial, we’ll create a Telegram Bot using Spring Boot.

A Telegram bot is an automated program that operates within the Telegram messaging platform. It utilizes the Telegram Bot API to interact with users and perform various tasks. We’ll use a Java library instead of interacting with the API directly. Bots help us respond to user commands, provide information, and perform automated actions.

We’ll start by setting up a brand-new bot and then go on to describe how to use the Java library to implement simple actions.

2. Creating a Telegram Bot

First, we need to create a new bot on the Telegram platform. We do it directly using the Telegram messaging application and searching for the BotFather in the search bar. Once open, we’ll type the /newbot command to create the bot and follow BotFather’s instructions. It’ll ask for the username we want to assign to the bot, which needs to end with a bot as per Telegram’s policy:

Telegram Bot

Above, the BotFather generated a token that we must save somewhere safe to use later to configure our application.

3. Setting Up the Application

Second, we must have a Spring Boot project where we want to integrate the Telegram Bot. We are going to modify the pom.xml file and include the telegrambots-spring-boot-starter and the telegrambots-abilities library:

<dependency>
    <groupId>org.telegram</groupId>
    <artifactId>telegrambots-spring-boot-starter</artifactId>
    <version>6.7.0</version>
</dependency>
<dependency>
    <groupId>org.telegram</groupId>
    <artifactId>telegrambots-abilities</artifactId>
    <version>6.7.0</version>
</dependency>

Under the hood, the AbilityBot uses webhooks to communicate with Telegrams APIs, but we don’t need to worry about that. In fact, the library implements all the interfaces provided by the Telegram Bot API.

Now, we can implement our bot.

4. Explaining the PizzaBot

We’ll implement a simple bot simulating a pizza shop to demonstrate using the library with Spring Boot. Moreover, we’ll have a predefined set of interactions with the bot:

Pizzabot Interaction diagram

In short, we’ll start by asking the user for their name. Then, we’ll prompt him to select a Pizza or a drink. In the case of drinks, we’ll display a message saying we don’t sell drinks. Otherwise, we’ll ask them for the toppings of the pizza. After selecting the available toppings, we’ll confirm if the user wants to order again. In this case, we’ll repeat the flow. Alternatively, we’ll thank them and close the chat with a closing message.

5. Configure and Register the PizzaBot

Let’s start by configuring an AbilityBot for our new PizzaShop:

@Component
public class PizzaBot extends AbilityBot {
    private final ResponseHandler responseHandler;
    @Autowired
    public PizzaBot(Environment env) {
        super(env.getProperty("botToken"), "baeldungbot");
        responseHandler = new ResponseHandler(silent, db);
    }
    @Override
    public long creatorId() {
        return 1L;
    }
}

We read the botToken property injected as an environment variable inside the constructor. We must keep the token safe and not push it into the codebase. In this example, we export it to our environment before running the application. Alternatively, we could define it inside the properties file. Moreover, we must provide a unique creatorId that describes our bot.

Also, we are extending the AbilityBot class which reduces the boilerplate code and offers commonly used tools such as state machines through ReplyFlow. However, we’ll only use the embedded database and manage the state explicitly inside ResponseHandler:

public class ResponseHandler {
    private final SilentSender sender;
    private final Map<Long, UserState> chatStates;

    public ResponseHandler(SilentSender sender, DBContext db) {
        this.sender = sender;
        chatStates = db.getMap(Constants.CHAT_STATES);
    }

}

5.1. Spring Boot 3 Compatibility Issue

When using version 3 of Spring Boot, the library doesn’t automatically configure the bot when declared as @Component. Therefore, we must initialize it manually inside our main application class:

TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
botsApi.registerBot(ctx.getBean("pizzaBot", TelegramLongPollingBot.class));

We create a new instance of the TelegramBotsApi and then provide the pizzaBot component instance from the Spring Application context.

6. Implementing the PizzaBot

The Telegram API is huge and can become repetitive to implement new commands. Consequently, we use abilities to simplify the process of developing new capabilities. We should consider the end user, the necessary conditions, and the execution process when designing the interaction flow.

In our PizzaBot, we’ll use just a single ability that’ll /start the conversation with the bot:

public Ability startBot() {
    return Ability
      .builder()
      .name("start")
      .info(Constants.START_DESCRIPTION)
      .locality(USER)
      .privacy(PUBLIC)
      .action(ctx -> responseHandler.replyToStart(ctx.chatId()))
      .build();
}

We use the builder pattern to build the Ability. First, we define the name of the Ability, which is the same as the command of the Ability. Second, we provide a description string of this new Ability. This will be helpful when we run the /commands command to get our bot’s abilities. Third, we define the locality and privacy of the bot. Finally, we define what action we must take upon receiving the command. For this example, we’ll forward the id of the chat to the ResponseHandler class. Following the design from the diagram above, we’ll ask for the name of the user and save it inside a map with its initial state:

public void replyToStart(long chatId) {
    SendMessage message = new SendMessage();
    message.setChatId(chatId);
    message.setText(START_TEXT);
    sender.execute(message);
    chatStates.put(chatId, AWAITING_NAME);
}

In this method, we create a SendMessage command and execute it using the sender. Then, we set the chat state to AWAITING_NAME, signaling we are waiting for the user’s name:

private void replyToName(long chatId, Message message) {
    promptWithKeyboardForState(chatId, "Hello " + message.getText() + ". What would you like to have?",
      KeyboardFactory.getPizzaOrDrinkKeyboard(),
      UserState.FOOD_DRINK_SELECTION);
}

After the user enters their name, we send them a ReplyKeyboardMarkup that prompts them with two options:

public static ReplyKeyboard getPizzaToppingsKeyboard() {
    KeyboardRow row = new KeyboardRow();
    row.add("Margherita");
    row.add("Pepperoni");
    return new ReplyKeyboardMarkup(List.of(row));
}

This will hide the keyboard and display the user an interface with two buttons:

PizzaBot Button menuNow, the user can select a pizza or drink that our pizza shop doesn’t offer. Telegram sends a text message with a response when selecting any of the two options.

For all the green diamond-shaped elements in the diagram above, we follow a similar process. Therefore, we won’t repeat it here. Instead, let’s focus on handling the response to the buttons.

7. Handling the Replies From the User

For all the incoming messages and the current state of the chat, we handle the response differently inside our PizzaBot class:

public Reply replyToButtons() {
    BiConsumer<BaseAbilityBot, Update> action = (abilityBot, upd) -> responseHandler.replyToButtons(getChatId(upd), upd.getMessage());
    return Reply.of(action, Flag.TEXT,upd -> responseHandler.userIsActive(getChatId(upd)));
}

The .replyToButtons() gets all the TEXT replies and forwards them to the ResponseHandler along with the chatId and the incoming Message object. Then, inside the ResponseHandler the .replyToButtons() method decides how to process the message:

public void replyToButtons(long chatId, Message message) {
    if (message.getText().equalsIgnoreCase("/stop")) {
        stopChat(chatId);
    }

    switch (chatStates.get(chatId)) {
        case AWAITING_NAME -> replyToName(chatId, message);
        case FOOD_DRINK_SELECTION -> replyToFoodDrinkSelection(chatId, message);
        case PIZZA_TOPPINGS -> replyToPizzaToppings(chatId, message);
        case AWAITING_CONFIRMATION -> replyToOrder(chatId, message);
        default -> unexpectedMessage(chatId);
    }
}

Inside the switch, we check the current status of the chat and reply to the user accordingly. For example, when the current state for the user is FOOD_DRINK_SELECTION, we process the response and move to the next state when the user taps on the option pizza:

private void replyToFoodDrinkSelection(long chatId, Message message) {
    SendMessage sendMessage = new SendMessage();
    sendMessage.setChatId(chatId);
    if ("drink".equalsIgnoreCase(message.getText())) {
        sendMessage.setText("We don't sell drinks.\nBring your own drink!! :)");
        sendMessage.setReplyMarkup(KeyboardFactory.getPizzaOrDrinkKeyboard());
        sender.execute(sendMessage);
    } else if ("pizza".equalsIgnoreCase(message.getText())) {
        sendMessage.setText("We love Pizza in here.\nSelect the toppings!");
        sendMessage.setReplyMarkup(KeyboardFactory.getPizzaToppingsKeyboard());
        sender.execute(sendMessage);
        chatStates.put(chatId, UserState.PIZZA_TOPPINGS);
    } else {
        sendMessage.setText("We don't sell " + message.getText() + ". Please select from the options below.");
        sendMessage.setReplyMarkup(KeyboardFactory.getPizzaOrDrinkKeyboard());
        sender.execute(sendMessage);
    }
}

Additionally, inside .replyToButtons(), we immediately check if the user sent the /stop command. In that case, we stop the chat and remove the chatId from the chatStates map:

private void stopChat(long chatId) {
    SendMessage sendMessage = new SendMessage();
    sendMessage.setChatId(chatId);
    sendMessage.setText("Thank you for your order. See you soon!\nPress /start to order again");
    chatStates.remove(chatId);
    sendMessage.setReplyMarkup(new ReplyKeyboardRemove(true));
    sender.execute(sendMessage);
}

This removes the user’s state from the database. To interact again, the user must write the /start command.

8. Conclusion

In this tutorial, we discussed implementing a Telegram bot using Spring Boot.

First, we created a new bot on the Telegram platform using BotFather. Second, we set up our Spring Boot project and explained the functionality of our PizzaBot and how it interacts with users. Then, we implemented the PizzaBot using abilities to simplify developing new commands. Finally, we handled the replies from users and provided appropriate responses based on the chat state.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments