Telegram Bots

Hosting a Java Telegram Bot With TelegramBots

Build a Telegram bot in Java with the TelegramBots library's long-polling application, package it as a runnable JAR, size the JVM and deploy it to run 24/7.

On this page
  1. Choose a Java version
  2. Dependencies
  3. The bot
  4. Handle errors per update
  5. Package a runnable JAR
  6. Size the JVM
  7. Deploy
  8. Graceful shutdown
  9. Webhooks and Spring Boot
  10. Troubleshooting
  11. Summary

Java is a solid choice for Telegram bots that need strong typing, mature libraries or integration with existing JVM systems. The most widely used library is TelegramBots (org.telegram). Recent major versions reorganised it into separate modules for long polling, webhooks and the HTTP client. This guide uses that modern structure, packages the bot as a single JAR and deploys it.

Choose a Java version

Use Java 17 or 21 — both are long-term-support releases, and current TelegramBots releases target modern Java. Kerit Cloud offers Java 11, 17 and 21; compile for the same version you’ll run.

Dependencies

With Maven, add the long-polling module and the OkHttp-based client, managing the version in one property:

<properties>
  <maven.compiler.release>21</maven.compiler.release>
  <!-- check Maven Central for newer releases -->
  <telegrambots.version>8.0.0</telegrambots.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.telegram</groupId>
    <artifactId>telegrambots-longpolling</artifactId>
    <version>${telegrambots.version}</version>
  </dependency>
  <dependency>
    <groupId>org.telegram</groupId>
    <artifactId>telegrambots-client</artifactId>
    <version>${telegrambots.version}</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.5.12</version>
  </dependency>
</dependencies>

Newer releases track new Bot API features, so check Maven Central and bump the version when you start a project. Logback gives the library’s SLF4J logging somewhere to go; without an implementation, you lose useful connection logs.

The bot

package bot;

import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient;
import org.telegram.telegrambots.longpolling.TelegramBotsLongPollingApplication;
import org.telegram.telegrambots.longpolling.util.LongPollingSingleThreadUpdateConsumer;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.generics.TelegramClient;

public class EchoBot implements LongPollingSingleThreadUpdateConsumer {

    private final TelegramClient client;

    public EchoBot(String token) {
        this.client = new OkHttpTelegramClient(token);
    }

    @Override
    public void consume(Update update) {
        if (!update.hasMessage() || !update.getMessage().hasText()) return;

        String text = update.getMessage().getText();
        long chatId = update.getMessage().getChatId();
        String reply = text.equals("/start") ? "Hello from a Java bot!" : "You said: " + text;

        try {
            client.execute(SendMessage.builder().chatId(chatId).text(reply).build());
        } catch (TelegramApiException e) {
            System.err.println("Failed to reply in chat " + chatId + ": " + e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        String token = System.getenv("BOT_TOKEN");
        if (token == null || token.isBlank()) throw new IllegalStateException("BOT_TOKEN is not set");

        try (TelegramBotsLongPollingApplication app = new TelegramBotsLongPollingApplication()) {
            app.registerBot(token, new EchoBot(token));
            System.out.println("Bot started");
            Thread.currentThread().join();   // keep the main thread alive
        }
    }
}

The structure separates concerns cleanly: TelegramBotsLongPollingApplication runs the polling loop, your class consumes updates, and a TelegramClient sends requests. The try-with-resources block closes the application — stopping polling — when the program exits.

LongPollingSingleThreadUpdateConsumer processes updates one at a time, which keeps things simple and avoids concurrency bugs. For high-volume bots, implement the multi-threaded consumer interface or hand work to your own executor.

Handle errors per update

An exception thrown from consume shouldn’t be allowed to disrupt the bot. Catch exceptions around each update’s processing, log them with the update ID, and move on. For Telegram API errors, inspect the error code:

  • 403 — the user blocked the bot; mark them inactive.
  • 429 — too many requests; wait the number of seconds Telegram returns before retrying.
  • 400 “message is not modified” — harmless; you edited with identical content.

Telegram’s sending limits are explained in Telegram Bot API limits.

Package a runnable JAR

The server needs one JAR containing your code and every dependency. With Maven, use the Shade plugin:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>3.6.0</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals><goal>shade</goal></goals>
          <configuration>
            <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>bot.EchoBot</mainClass>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
            </transformers>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

mvn package produces a JAR in target/ you can run with java -jar. The ServicesResourceTransformer merges service files from dependencies, which some libraries rely on. Gradle users can do the same with the Shadow plugin, as shown in hosting a JDA Java Discord bot.

Size the JVM

The JVM needs more memory than just its heap — metaspace, threads and native buffers all count toward your plan’s limit. If the heap is set equal to the plan’s RAM, the process exceeds the limit and is killed. Let the JVM size the heap from the container limit:

java -XX:MaxRAMPercentage=75 -jar bot.jar

A simple long-polling bot runs comfortably in 512 MB–1 GB. Kerit Cloud’s Starter Telegram plan (1 GB RAM, a full EPYC core) is a good home for most Java bots; the JVM’s startup and baseline memory make the free plan’s 256 MB tight for Java.

Deploy

  1. Create a server with a Java 17 or 21 runtime matching your build.
  2. Upload the shaded JAR via the file manager or SFTP — or link your repository on a paid plan and build with Maven during deploy.
  3. Add BOT_TOKEN as an environment variable (stored encrypted, never logged).
  4. Set the startup command to java -XX:MaxRAMPercentage=75 -jar bot.jar.
  5. Start the server and watch for “Bot started”, then message your bot.

If the process crashes, the watchdog restarts it within seconds, and the stack trace stays in the console.

Graceful shutdown

Hosts send SIGTERM on restarts and redeploys. The JVM runs shutdown hooks on SIGTERM, and the try-with-resources block above closes the polling application as the program exits. If you hold other resources — a database pool, an executor — close them in a shutdown hook too:

Runtime.getRuntime().addShutdownHook(new Thread(() -> dataSource.close()));

Webhooks and Spring Boot

TelegramBots also provides a webhook module for bots that receive updates over HTTPS, and Spring Boot starters if your bot lives inside a Spring application. The update-consuming code stays much the same; only the application wrapper changes. The trade-offs between the two modes are covered in long polling vs webhooks.

Troubleshooting

UnsupportedClassVersionError — you compiled for a newer Java than the server runs. Match maven.compiler.release to the runtime.

NoClassDefFoundError at startup — the JAR doesn’t include dependencies. Build the shaded JAR, not the plain one.

The process disappears without an error — the JVM exceeded the memory limit. Use MaxRAMPercentage=75 or a lower -Xmx.

409 Conflict — another copy of the bot is polling. Stop it.

Summary

Build a Java Telegram bot with TelegramBots’ long-polling application, a LongPollingSingleThreadUpdateConsumer and the OkHttp client, reading the token from an environment variable. Package it as a shaded JAR, run it on Java 17 or 21 with a heap of about 75% of your plan’s memory, catch errors per update, and close resources on shutdown. The result is a fast, type-safe bot that runs 24/7.