NEW 📢 Limited launch offer: up to 70% off your first month on every game!See plans

How to host a TelegramBots (Java) bot 24/7

Telegram bot hostingUpdated Nov 6, 202613 min read

To host a TelegramBots (Java) bot 24/7 you run it on a machine that stays on: a Linux VPS with a systemd service that starts it again when it stops, or a bot plan on PlayNHost Telegram bot hosting (from US$1.99 a month), where you upload one runnable jar, paste the token from @BotFather and press Start. In our test a bot on TelegramBots 10.3.0, the Java library by rubenlagus, used about 91 MB of memory on Java 21 and got Telegram's answer 1.5 seconds after it was launched. Use its long polling module: our plans have no public HTTPS address for a webhook yet. The steps on PlayNHost:

  1. On the Telegram bot hosting page, click Create Server on a plan. In the order window, answer What is your bot for? with Telegram and pick Java.
  2. On your computer, build the bot with the pom.xml below: mvn package writes target/bot.jar, with TelegramBots inside.
  3. Open your server in the panel. The Set up your bot card on Overview opens at Connect to Telegram; its first step, What runs, should say This bot runs Java Bot.
  4. At Connect to Telegram, paste the token under Paste your bot token, click Connect, check the bot name shown and click Yes, continue.
  5. At Your code, click Upload files and upload bot.jar to the main folder.
  6. On the Bot page, pane Code, check that Entry file is bot.jar and Runtime is Java 21 (recommended), or Java 17 or 25. If you change Entry file, click Save next to it; a new Runtime is saved as soon as you pick it.
  7. Click Start bot and open Console. When your bot prints Logged in as @your_bot, the chip on Overview turns to Online in Telegram.
  8. On the Bot page, pane Telegram, click Open in Telegram and send your bot /start.

Creating the bot in @BotFather and the panel steps are explained in How to host a Telegram bot. This page is about TelegramBots itself: what it uses on a server and what to know before you host it.

What we measured

LibraryVersionMemory at restLaunch to Telegram's answerJar size
TelegramBots (rubenlagus)10.3.091 MB1.5 s9.6 MB
Java 21 alone, no library21.0.12.138 MBnone0.0 MB

How: on our own node, in a throwaway container set up like a 1 GB bot plan (the Java 21 image and the exact java command of our Java bot type), the bot called Telegram's getMe with a fake token, which Telegram refused; memory is the resident memory 15 seconds after that answer, start time runs from launch to the answer, and each figure is the median of runs 2 to 4.

These are floors: no chats, no data and no polling yet. Your own code, what it keeps in memory and the number of chats add to them, and the start script lets the Java heap grow up to 80 percent of the plan (see below). Compare the numbers with the plans on Telegram bot hosting and choose by what your bot does.

A minimal TelegramBots bot

It answers /start, reads the token from TELEGRAM_BOT_TOKEN (the panel gives it to your code under that name and also as BOT_TOKEN, TOKEN, TELEGRAM_TOKEN and TELOXIDE_TOKEN) and prints a ready line. Two files:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>bot</artifactId>
  <version>1.0</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.telegram</groupId>
      <artifactId>telegrambots-longpolling</artifactId>
      <version>10.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.telegram</groupId>
      <artifactId>telegrambots-client</artifactId>
      <version>10.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>2.0.12</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>bot</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.7.1</version>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <appendAssemblyId>false</appendAssemblyId>
          <archive>
            <manifest>
              <mainClass>Bot</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

src/main/java/Bot.java

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

public class Bot extends DefaultLongPollingUpdateConsumer {
    private final TelegramClient client;

    Bot(TelegramClient client) {
        this.client = client;
    }

    @Override
    public void consume(Update update) {
        if (update.hasMessage() && update.getMessage().hasText()
                && update.getMessage().getText().startsWith("/start")) {
            SendMessage reply = SendMessage.builder()
                    .chatId(update.getMessage().getChatId())
                    .text("Hello! I am running on PlayNHost.")
                    .build();
            try {
                client.execute(reply);
            } catch (TelegramApiException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        String token = System.getenv("TELEGRAM_BOT_TOKEN");
        if (token == null || token.isBlank()) {
            System.out.println("No TELEGRAM_BOT_TOKEN found. Paste your bot token on the Bot page, then restart.");
            System.exit(1);
        }

        TelegramClient client = new OkHttpTelegramClient(token);
        User me = null;
        try {
            me = client.execute(new GetMe());
        } catch (TelegramApiRequestException e) {
            if (e.getErrorCode() != null && e.getErrorCode() == 401) {
                System.out.println("Telegram did not accept the bot token. Paste the current one from @BotFather on the Bot page, then restart.");
                System.exit(1);
            }
            throw e;
        }

        try (TelegramBotsLongPollingApplication app = new TelegramBotsLongPollingApplication()) {
            // Long polling: registerBot removes a webhook left on the bot, then asks Telegram for messages.
            app.registerBot(token, new Bot(client));
            System.out.println("Logged in as @" + me.getUserName());
            Thread.currentThread().join();
        }
    }
}

Build it with Maven 3.9 and a JDK 17 or newer, and test the jar on your computer:

mvn package
java -jar target/bot.jar

Without a token it prints No TELEGRAM_BOT_TOKEN found. and stops, which proves the jar starts and finds its main class. Upload target/bot.jar. Stop any copy you run on your computer with the real token before you start the one on the server, or the two copies get 409 Conflict errors from Telegram.

The getMe call checks the token before polling starts and gives the bot's username for the ready line. Logged in as @your_bot is the line that turns the chip to Online in Telegram.

If the token is wrong

In our test, with a fake token, TelegramBots' getMe call failed with this error:

TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.GetMe query: [401] Unauthorized

The bot above catches that refusal (error code 401) and prints Telegram did not accept the bot token. Paste the current one from @BotFather on the Bot page, then restart. instead. The panel reads that line and shows the banner Telegram did not accept your bot token above Console. To fix it, in @BotFather send /mybots, choose your bot, then API Token, and copy the token again. On the Bot page, pane Telegram, click Replace token, paste it, click Save token, then Restart now.

What to know about TelegramBots on a server

  • Versions: TelegramBots 10.3.0 came out on 7 Sep 2026 and supports Telegram's Bot API 10.3. It needs Java 17 or newer, so every Runtime the panel offers (Java 17, 21 or 25) runs it.
  • Two packages, unlike older tutorials. Since version 7.0.0 (March 2024) the library comes as telegrambots-longpolling, which receives messages, and telegrambots-client, which sends them. Tutorials that extend TelegramLongPollingBot were written for the old telegrambots package, last released as 6.9.7.1; that class is not in 10.3.0, so their code does not compile against it.
  • Use long polling. The library also has a webhook module, but a webhook needs a public HTTPS address, which our plans do not include yet. registerBot in the long polling module calls deleteWebhook before it starts, so a webhook left from an old host is removed and the messages waiting are kept. Moving from a webhook host: How to move a Telegram bot to PlayNHost.
  • Add a logging library, or the library is silent. TelegramBots logs through SLF4J. With no SLF4J provider in your jar, SLF4J prints a warning once and then throws every library log line away, including the errors from polling. The pom.xml above adds slf4j-simple, which writes the library's lines at INFO and above to standard error, so they show in Console.
  • Polling errors are retried, not thrown. When a getUpdates call fails, TelegramBots logs Error received from Telegram GetUpdates Request, retrying in ... millis and tries again, so the process keeps running. The wait starts at about half a second and grows by half each time up to about a minute (with a random spread, up to 90 seconds); once 15 minutes have passed without a successful call, it waits 15 minutes between tries. So after you fix the cause, restart the bot rather than wait. Only the HTTP status code, in the Caused by line of the stack trace under it, says what went wrong; Telegram's own description of the error is not printed. Because registerBot already removed any webhook, a 409 there most likely means another copy of the bot is polling with the same token: stop it. See Fix Telegram bot 409 Conflict.
  • The official lesson, updated: it implements LongPollingSingleThreadUpdateConsumer, which 10.3.0 marks as deprecated in favour of the class DefaultLongPollingUpdateConsumer used above; both handle updates one at a time on a background thread. Its ready line, MyAmazingBot successfully started!, does not turn the chip to Online in Telegram: print a line that starts with Logged in as, as above.
  • Keep main() waiting. Polling runs on the library's own threads; the lesson ends main() with Thread.currentThread().join() so the program does not finish, and the bot above does the same.
  • Memory limit: the start script runs Java with -XX:MaxRAMPercentage=80.0 and the G1 garbage collector, so the heap can use up to 80 percent of your plan's memory. If it fills up, Java prints java.lang.OutOfMemoryError and the panel shows Your bot ran out of memory.
  • One jar, nothing installed on the server. The Java bot type starts java -jar on your Entry file and has no Packages field, so everything must be inside the jar. Upload a new jar to update the bot.

Host it yourself on a Linux VPS

A VPS needs only a Java runtime, because you build the jar on your own computer. On Debian 13 or Ubuntu 24.04 install Java 21; Debian 12 ships Java 17, which also runs the jar above:

sudo apt update
sudo apt install openjdk-21-jre-headless
java -version

Make a user for the bot and copy the jar to the server (for example with scp), then into its folder:

sudo useradd --system --create-home --home-dir /opt/mybot mybot
sudo cp bot.jar /opt/mybot/
sudo chown -R mybot: /opt/mybot

Keep the token out of your code, in a file only root can read:

sudo sh -c 'echo "TELEGRAM_BOT_TOKEN=123456789:your-token" > /etc/mybot.env'
sudo chmod 600 /etc/mybot.env

Create /etc/systemd/system/mybot.service:

[Unit]
Description=TelegramBots Telegram bot
Wants=network-online.target
After=network-online.target

[Service]
User=mybot
WorkingDirectory=/opt/mybot
EnvironmentFile=/etc/mybot.env
ExecStart=/usr/bin/java -XX:MaxRAMPercentage=80.0 -jar /opt/mybot/bot.jar
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Start it now and at every boot, then follow its output:

sudo systemctl daemon-reload
sudo systemctl enable --now mybot
sudo journalctl -u mybot -f

Restart=always starts the bot again 5 seconds after it exits or crashes, but not after you stop it with systemctl stop. On a VPS, -XX:MaxRAMPercentage is a share of the whole machine's memory, so lower it if other programs run there too. To update, copy the new jar over the old one and run sudo systemctl restart mybot.

Self-hosting or a bot plan

On a VPS you manage yourself:

  • Operating system updates and security, and Java upgrades.
  • Copying each new jar, the service file, the token file and its permissions.
  • Reading journalctl to find out why the bot stopped or stopped answering.
  • Backups of anything your bot stores.

In return a VPS runs anything, and you can give it a domain with HTTPS for a webhook.

On a PlayNHost bot plan the panel does this for you:

  • Java 17, 21 or 25 from a picker, with the heap limit and garbage collector set.
  • The token, checked with Telegram before it is saved, stored encrypted and given to your code under five names.
  • Starting the bot again after a crash. If it crashes again within a minute, it stays stopped and Console says why.
  • A Remove webhook button on the Telegram pane, and banners above Console that name the cause, such as a refused token or a lack of memory.
  • Upload from the browser, and backups from the panel.

Questions people ask

My TelegramBots bot runs but answers nothing and prints nothing. Why?

Most often the jar has no SLF4J provider, so the library's own error lines are thrown away while it keeps retrying. Add slf4j-simple as in the pom.xml above, build again and look at Console. A repeated 409 means another copy of the bot is polling with the same token; a repeated 401 means the token was revoked in @BotFather. After you fix it, restart the bot: TelegramBots' wait between tries can reach 15 minutes.

Can I use the TelegramBots webhook module?

Not on our plans yet: a webhook needs a public HTTPS address, which they do not include. Long polling needs none, because the bot asks Telegram for new messages. Use telegrambots-longpolling as on this page.

Does TelegramBots need a bigger plan than a Python or Node.js bot?

Not for the library itself. In the same test, run the same way, TelegramBots used 91 MB, python-telegram-bot 22.8 used 44 MB, grammY 1.46.0 used 60 MB and aiogram 3.31.0 used 173 MB, all well under the 0.5 GB of the smallest plan. What your bot keeps in memory decides the rest. On Java, memory also depends on the heap: the start script lets it grow up to 80 percent of the plan, and the garbage collector decides how much of that it uses.

Which Java version should I pick?

Java 21, the recommended one, unless your own code needs 25. TelegramBots 10.3.0 needs Java 17 or newer. Build with a release no newer than the Runtime you pick: a jar built for a newer Java stops with UnsupportedClassVersionError.

Ready to run your TelegramBots bot around the clock? See the plans on Telegram bot hosting.

Already hosting your bot somewhere else? Move it to us

OUR LATEST BLOGS