So what are bots? You can read more about this here. To start, you need to review the official documentation for the library used to develop Telegram bots (hereafter the "API"). It can be found here.
Everything there is very accessible and clear. It seems we can just write code and rejoice! But it's not so simple. After spending a lot of time searching, I've found tidbits of knowledge on bot development, for example, how to make a keyboard, handle a CallbackQuery, and so forth. But, I didn't find a complete, exhaustive guide for Java bot development. That prompted me to write this article. There are a lot of websites where you can create your own easily deployable bot. But the fact is, most of the bots created provide reference information and the like. Our bot is a full-fledged web application. You can bind a database, execute various API requests, parse websites, perform complex calculations, and more. The possibilities are limited only by your imagination. I hope that the above has helped clarify what I'm going to write about. It's very easy to register a bot on Telegram. This process is described in detail in the documentation linked to above. For our application, you only need to know the bot's name and the token you receive when registering the bot. Basically, a bot is just a console-based web application. There is no front end, just pure command processing. If you want to master Hibernate or learn how to parse JSON, then this is the project for you. Let's start by adding a dependency to pom.xml (I'll assume you're using Maven). You can do that like this:
<dependency>
            <groupId>org.telegram</groupId>
            <artifactId>telegrambots</artifactId>
            <version>3.5</version>
</dependency>
Then create a Bot class that inherits the TelegramLongPollingBot class, and override its methods:
public class Bot extends TelegramLongPollingBot {

    /**
     * Method for receiving messages.
     * @param update Contains a message from the user.
     */
    @Override
    public void onUpdateReceived(Update update) {
	String message = update.getMessage().getText();
	sendMsg(update.getMessage().getChatId().toString(), message);
    }

    /**
     * Method for creating a message and sending it.
     * @param chatId chat id
     * @param s The String that you want to send as a message.
     */
    public synchronized void sendMsg(String chatId, String s) {
        SendMessage sendMessage = new SendMessage();
        sendMessage.enableMarkdown(true);
        sendMessage.setChatId(chatId);
        sendMessage.setText(s);
        try {
            sendMessage(sendMessage);
        } catch (TelegramApiException e) {
            log.log(Level.SEVERE, "Exception: ", e.toString());
        }
    }

    /**
     * This method returns the bot's name, which was specified during registration.
     * @return bot name
     */
    @Override
    public String getBotUsername() {
        return "BotName";
    }

    /**
     * This method returns the bot's token for communicating with the Telegram server
     * @return the bot's token
     */
    @Override
    public String getBotToken() {
        return "BotToken";
    }
}
And now the contents of the main method:
public static void main(String[] args) {
        ApiContextInitializer.init();
        TelegramBotApi telegramBotApi = new TelegramBotApi();
        try {
            telegramBotApi.registerBot(Bot.getBot());
        } catch (TelegramApiRequestException e) {
            e.printStackTrace();
        }
}
After filling in the getBotUsername() and getBotToken() methods, start the bot. For now, it just redirects to us any messages we send to it, kind of like a "mirror". It works like this: when you start the application, it starts sending requests to the Telegram server, once every n seconds, at the following URL: https://api.telegram.org/BotToken/getMe, where BotToken is your bot's token. In response, it receives JSON containing all the messages. Each of these messages is processed by the library and is passed to the OnUpdateReceived(Update update) method as an Update object. And that's what we work with. Herein lies the beauty of Telegram bots: they can run on any computer, testing it just requires starting the application, and you don't need to deploy it to the host after each change. This is very convenient. Of course, you can configure a bot to work using webhooks. You can find directions for that on the Internet. For simplicity, we'll use LongPolling. How messages are processed and what you send in response are only limited by the capabilities of the language and library. Everything else is up to you. You can make a bot that will search YouTube videos for you. You can make a bot that will send you what you send yourself everyday, like a time capsule from a year ago. Or you can learn to integrate with CRM systems and make bots for small businesses—you're only limited by your imagination. Moving on. Those who have used bots know that it's convenient to interact with them using commands that start with the "/" character, for example, /start. But there is a more convenient way: buttons. There are two kinds of buttons: those that appear under an input field (ReplyKeyboardMarkup) and buttons that are directly below the message they're linked to (InlineKeyboardMarkup). You can get a basic understanding of them from their descriptions in the documentation.

ReplyKeyboardMarkup

In reality, this is an array of button arrays: List<KeyboardRow <KeyboardButton>>. Here's sample code that creates a keyboard:
public synchronized void setButtons(SendMessage sendMessage) {
        // Create a keyboard
        ReplyKeyboardMarkup replyKeyboardMarkup = new ReplyKeyboardMarkup();
        sendMessage.setReplyMarkup(replyKeyboardMarkup);
        replyKeyboardMarkup.setSelective(true);
        replyKeyboardMarkup.setResizeKeyboard(true);
        replyKeyboardMarkup.setOneTimeKeyboard(false);

        // Create a list of keyboard rows
        List<KeyboardRow> keyboard = new ArrayList<>();

        // First keyboard row
        KeyboardRow keyboardFirstRow = new KeyboardRow();
        // Add buttons to the first keyboard row
        keyboardFirstRow.add(new KeyboardButton("Hi"));

        // Second keyboard row
        KeyboardRow keyboardSecondRow = new KeyboardRow();
        // Add the buttons to the second keyboard row
        keyboardSecondRow.add(new KeyboardButton("Help");

        // Add all of the keyboard rows to the list
        keyboard.add(keyboardFirstRow);
        keyboard.add(keyboardSecondRow);
        // and assign this list to our keyboard
        replyKeyboardMarkup.setKeyboard(keyboard);
    }
We call this method in the sendMsg() method, after passing it a message. This is how we set up a keyboard for that message. When we send this message to the user, he will see the text of our message and 2 buttons that say "Hi" and "Help", one under the other. When one of these buttons is clicked, the bot is sent a message containing the button text. So, if the client clicks "Help", the bot will receive a message with "Help". To the bot, it will seem that the client itself wrote "Help" and sent the text to the bot. And then you process the messages.

InlineKeyboardMarkup

This is also an array of arrays. It is similar to the previous Markup, but the logic works a little differently here. This type of keyboard is attached to a specific message and exists only for it. Here's a method for setting up an inline keyboard:
private void setInline() {
        List<List<InlineKeyboardButton>> buttons = new ArrayList<>();
        List<InlineKeyboardButton> buttons1 = new ArrayList<>();
        buttons1.add(new InlineKeyboardButton().setText("Button").setCallbackData(17));
        buttons.add(buttons1);

        InlineKeyboardMarkup markupKeyboard = new InlineKeyboardMarkup();
        markupKeyboard.setKeyboard(buttons);
    }
Create a List within the List, and add the inline button to the first row. This button can contain a URL, a link to a channel, or a CallbackQuery, which I'll write about a little later. This is where we set our button's text, which is what the user will see, and then we set the data that will be sent to the bot. In our example, the user sees "Hi", and when the button is pressed the number 17 will be sent to the bot. This is our CallbackQuery. A few words regarding CallbackQuery. To obtain this data from the Update object, you need to execute update.getCallbackQuery(). This method returns a CallbackQuery, from which you can access the data passed to the bot. Don't try to get this data through the update.getMessage().getText() method—you'll get a NullPointerException.
@Override
    public void onUpdateReceived(Update update) {
        if(update.hasMessage()) {
            ThreadClass thread = new ThreadClass(update.getMessage());
        } else  if(update.hasCallbackQuery()) {
            AnswerCallbackThread answerThread = new AnswerCallbackThread(update.getCallbackQuery());
        }
    }
If there's a message, we send it to a new thread for processing; if there's a CallbackQuery, we send to the appropriate thread for processing. You can send a response to a CallbackQuery. Every object in Telegram has its own id. To send a response to a specific CallbackQuery, you just need to know its id, which we get from the corresponding object. To send a response, we call this method:
public synchronized void answerCallbackQuery(String callbackId, String message) {
        AnswerCallbackQuery answer = new AnswerCallbackQuery();
        answer.setCallbackQueryId(callbackId);
        answer.setText(message);
        answer.setShowAlert(true);
        try {
            answerCallbackQuery(answer);
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
    }
IMPORTANT: The text in a response to a CallbackQuery must be no longer than 200 characters! After sending such a response, the client will see a pop-up window containing the message. This window can disappear a few seconds after it appears, or it can persist until the user presses OK. To switch modes, we call the answer.setShowAlert(true) method. If you pass true to the method, the window persists until OK is pressed. If you pass false, then it disappears after 5 seconds. These are all of the Telegram Bot library's basic features. If you want, you can learn things like how to send multimedia, geolocation, etc. Let's move on to deploying our bot to a hosting platform. For my project, I chose Heroku. I think it's a rather convenient hosting platform with its own CLI. It's free, but on this plan your bot will hibernate after 30 minutes of receiving no requests. It will wake up when a request is received. This happens so quickly, you won't even notice (unless, of course, the database connection doesn't restart). The free plan is limited by a 5MB database, 100MB disk space, 2TB data per month, and 1 dyno. A dyno is your running application. I'll say right away that it was deployment that caused me difficulties, since I had never deployed my applications before. During deployment, Heroku requires a file named Procfile (without an extension). We create it in the project root. Inside, we write worker: sh target/bin/workerBot, where workerBot is the name specified in pom.xml. A sh script, generated by the Maven Application Assembler Plugin (appassembler-maven-plugin), will launch. The script describes how to launch a compiled jar file. The name of the class to launch is specified between <mainClass> and </ mainClass>, while the name of the script is given between <name> and </name> in pom.xml:
...
<build>
    <plugins>
        ...
       <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>appassembler-maven-plugin</artifactId>
            <version>1.1.1</version>
            <configuration>
                <assembleDirectory>target</assembleDirectory>
                <programs>
                    <program>
                        <mainClass>com.home.server.TelegramBot</mainClass>
                        <name>workerBot</name>
                    </program>
                </programs>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>assemble</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
Before starting this process, you should register on Heroku, and install both Git and the Heroku CLI. If your application needs a database, then don't forget to add the required database when you create a new application. Further, you need to determine the host, username, password and port for your database, and then indicate them in your application. Next, before deploying, build your project using Maven.
mvn clean install
First, we go to our project directory and initialize the repository with the command git init. Then we add our project to this repository.
git add .
Then we commit the changes
git commit -m "First commit in the project"
Next, you need to sign in to heroku. Write the following on the command line
heroku login
Enter the credentials you created during registration. After that, determine your repository's URL on heroku. You do this in the settings. Then we write
git remote add heroku [url]
A remote heroku repository is added for your repository. Next we write
git push heroku master
Then we wait… if the application deploys successfully, execute the following command
heroku ps:scale worker=1
And that's it, your application is up and running. If it isn't, look at the logs carefully. Most likely, an error in your application has caused it to fail. Thank you for reading such a long article. I hope someone will find it useful and that it will save you a lot of time in the areas that gave me trouble during development.