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:
- 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.
- On your computer, build the bot with the
pom.xmlbelow:mvn packagewritestarget/bot.jar, with TelegramBots inside. - 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.
- At Connect to Telegram, paste the token under Paste your bot token, click Connect, check the bot name shown and click Yes, continue.
- At Your code, click Upload files and upload
bot.jarto the main folder. - On the Bot page, pane Code, check that Entry file is
bot.jarand 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. - Click Start bot and open Console. When your bot prints
Logged in as @your_bot, the chip on Overview turns to Online in Telegram. - 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
| Library | Version | Memory at rest | Launch to Telegram's answer | Jar size |
|---|---|---|---|---|
| TelegramBots (rubenlagus) | 10.3.0 | 91 MB | 1.5 s | 9.6 MB |
| Java 21 alone, no library | 21.0.12.1 | 38 MB | none | 0.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, andtelegrambots-client, which sends them. Tutorials that extendTelegramLongPollingBotwere written for the oldtelegrambotspackage, 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.
registerBotin the long polling module callsdeleteWebhookbefore 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.xmlabove addsslf4j-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
getUpdatescall fails, TelegramBots logsError received from Telegram GetUpdates Request, retrying in ... millisand 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 theCaused byline of the stack trace under it, says what went wrong; Telegram's own description of the error is not printed. BecauseregisterBotalready 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 classDefaultLongPollingUpdateConsumerused 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 withLogged in as, as above. - Keep
main()waiting. Polling runs on the library's own threads; the lesson endsmain()withThread.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.0and the G1 garbage collector, so the heap can use up to 80 percent of your plan's memory. If it fills up, Java printsjava.lang.OutOfMemoryErrorand the panel shows Your bot ran out of memory. - One jar, nothing installed on the server. The Java bot type starts
java -jaron 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
journalctlto 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.



