Migrating from 0.0.5 to 0.0.6
Overview
Version 0.0.6 contains breaking changes (template type removal) alongside new additive features:
- BREAKING —
PlainTextTemplateremoved — migrate toPlainReplywith{n}MessageFormat args - BREAKING —
LocalizedTemplateremoved — migrate toLocalizedReplywith args orBotMessageSource @BotParseModeannotation — control Telegram parse mode from any handler method- sendMessage delivery options —
disableNotification,protectContent,messageThreadId,replyParameters,linkPreviewOptionsonPlainReplyandLocalizedReply - Configurable Telegram API URL — redirect requests to a local or self-hosted Bot API server
- Messaging factory provider SPI — inject custom Kafka / RabbitMQ factory instances into Easygram
BREAKING — PlainTextTemplate removed
PlainTextTemplate and BotPlainTextTemplateReturnTypeHandler have been removed. Migrate to
PlainReply, which now has built-in MessageFormat arg support. Token notation changes:
#{n} → {n}.
// Before
return PlainTextTemplate.of("Welcome, #{0}! You have #{1} messages.", name, count);
// After
return PlainReply.of("Welcome, {0}! You have {1} messages.", name, count);
Builder and wither equivalents:
PlainReply.builder().text("Hi, {0}!").args(name).build();
PlainReply.of("Hi, {0}!").withArgs(name);
BREAKING — LocalizedTemplate removed
LocalizedTemplate and BotLocalizedTemplateReturnTypeHandler have been removed. Also update
.properties bundle files to use standard {n} placeholders instead of #{n}.
Option 1 — single key with positional args:
// Before
return LocalizedTemplate.of("${register.complete} #{0}", city);
// After
return LocalizedReply.of("register.complete", city);
// messages/bot_en.properties: register.complete=Registration complete. City: {0}
Option 2 — multi-key messages (inject BotMessageSource):
// Before
return LocalizedTemplate.of("${welcome.title}\n\n${welcome.body}\n\nHello, #{0}!", name);
// After
@BotController
@RequiredArgsConstructor
public class WelcomeController {
private final BotMessageSource messageSource;
@BotCommand("/start")
public String onStart(User user, BotRequest request) {
return messageSource.getMessage("welcome.title", request) + "\n\n"
+ messageSource.getMessage("welcome.body", request) + "\n\n"
+ "Hello, " + user.getFirstName() + "!";
}
}
Bundle file migration — change #{n} → {n} in every .properties file:
# Before
greeting=Hello, #{0}!
# After
greeting=Hello, {0}!
New Feature 1 — @BotParseMode Annotation
Place @BotParseMode on any handler method to set the Telegram parse_mode field on the
outgoing SendMessage or EditMessageText call. Valid values are "HTML", "MarkdownV2",
and "Markdown" (legacy).
With String returns
@BotParseMode("HTML")
@BotCommand("/start")
public String start(User user) {
return "<b>Hello, " + user.getFirstName() + "!</b>";
}
@BotParseMode("MarkdownV2")
@BotText("help")
public String help() {
return "*Bold* and _italic_ text";
}
With PlainReply
@BotParseMode("HTML")
@BotCommand("/menu")
public PlainReply menu() {
return PlainReply.of("<b>Choose an option:</b>")
.withMarkup("main_menu");
}
With LocalizedReply (core-i18n)
@BotParseMode("HTML")
@BotCommand("/profile")
public LocalizedReply profile() {
return LocalizedReply.of("profile.text");
// messages/bot_en.properties: profile.text=<b>Your profile</b>
}
Fluent API (without annotation)
You can also set parse mode directly on the reply object — useful when the mode depends on runtime conditions:
public PlainReply reply(boolean useHtml) {
String text = useHtml ? "<b>Bold</b>" : "**Bold**";
String mode = useHtml ? "HTML" : "MarkdownV2";
return PlainReply.of(text).withParseMode(mode);
}
Both PlainReply and LocalizedReply support .withParseMode(String):
PlainReply.of("<b>text</b>").withParseMode("HTML")
LocalizedReply.of("msg.key").withParseMode("HTML") // core-i18n
Combining with @BotReplyMarkup
@BotParseMode and @BotReplyMarkup can appear on the same method in any order:
@BotParseMode("HTML")
@BotReplyMarkup("main_menu")
@BotCommand("/start")
public String start() {
return "<b>Welcome!</b> Choose an option:";
}
New Feature 2 — sendMessage Delivery Options
PlainReply and LocalizedReply now expose five additional sendMessage control fields
as wither methods and Builder setters. All are null by default — fully backward-compatible.
| Field | Type | Purpose |
|---|---|---|
disableNotification | Boolean | Send silently (no sound/vibration) |
protectContent | Boolean | Disable forwarding and saving |
messageThreadId | Integer | Target a forum topic thread |
replyParameters | ReplyParameters | Reply to a specific message |
linkPreviewOptions | LinkPreviewOptions | Control link preview display |
// Send silently
return PlainReply.of("Quiet update.").withDisableNotification(true);
// Reply to a message in a forum thread
return PlainReply.of("Done!")
.withMessageThreadId(topicId)
.withReplyParameters(ReplyParameters.builder().messageId(origId).build());
// Builder — protect content
return LocalizedReply.builder()
.key("welcome")
.protectContent(true)
.build();
New Feature 3 — Configurable Telegram API URL
Easygram now supports redirecting API requests to a custom URL — useful for local Bot API servers, test environments, or corporate proxies.
Properties
easygram:
telegram-url:
host: my-local-bot-api.example.com # custom hostname; omit to use Telegram's default
port: 8443 # optional; only used when host is set
schema: https # optional; only used when host is set
test-server: false # set to true to use Telegram test environment
All four properties are optional. When host is absent, TelegramUrl.DEFAULT_URL is used
(the standard api.telegram.org).
SPI override
For complete control, provide a BotTelegramUrlProvider bean — it takes precedence over
the properties above:
@Bean
public BotTelegramUrlProvider customTelegramUrl() {
return () -> new TelegramUrl("https", "my-bot-api.example.com", 443);
}
New Feature 4 — Messaging Factory Provider SPI
Three new SPI interfaces let you inject a fully configured Kafka or RabbitMQ factory instance, overriding the Spring Boot auto-configured defaults.
BotKafkaProducerFactoryProvider
Replaces the ProducerFactory used to build the Kafka template:
@Bean
public BotKafkaProducerFactoryProvider kafkaProducerFactory(SslBundles sslBundles) {
return () -> {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-cluster:9093");
// custom SSL, SASL, etc.
return new DefaultKafkaProducerFactory<>(props);
};
}
BotKafkaConsumerFactoryProvider
Replaces the ConsumerFactory used to build the Kafka listener:
@Bean
public BotKafkaConsumerFactoryProvider kafkaConsumerFactory() {
return () -> {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-cluster:9093");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-consumer-group");
return new DefaultKafkaConsumerFactory<>(props);
};
}
BotRabbitConnectionFactoryProvider
Replaces the RabbitMQ ConnectionFactory used for both publishing and consuming:
@Bean
public BotRabbitConnectionFactoryProvider rabbitConnectionFactory() {
return () -> {
CachingConnectionFactory factory = new CachingConnectionFactory("rabbit-cluster");
factory.setVirtualHost("/my-vhost");
factory.setUsername("bot-user");
factory.setPassword("secret");
return factory;
};
}
All three providers are @ConditionalOnMissingBean — declaring any one of them replaces
only that factory; the other two continue to use defaults.
Topic and exchange are now optional
easygram.messaging.kafka.topic and easygram.messaging.rabbit.queue /
easygram.messaging.rabbit.exchange are now fully optional. The defaults are:
| Property | Default |
|---|---|
easygram.messaging.kafka.topic | easygram-updates |
easygram.messaging.rabbit.queue | easygram-updates |
easygram.messaging.rabbit.exchange | easygram-exchange |
easygram.messaging.rabbit.routing-key | easygram.updates |
No change is required if you were already relying on these defaults.
New Feature 5 — BotReplyAction SPI (Internal refactor, fully additive)
The internals of PlainReply and LocalizedReply dispatch have been refactored to use a
filter-chain pattern. This is a non-breaking, additive change — all existing bot code
continues to work without modification.
What changed internally
Previously, BotPlainReplyReturnTypeHandler and BotLocalizedReplyReturnTypeHandler
contained a hard-coded if/else block that chose between sendMessage,
editMessageText, and answerCallbackQuery. Extending the dispatch logic required
modifying framework code.
Now both handlers delegate to BotReplyActionChain — a sorted list of BotReplyAction
beans:
BotReplyActionChain (order-sorted)
├── SendMessageReplyAction order=10
├── EditMessageReplyAction order=10
└── AnswerCallbackQueryReplyAction order=20
ReplyOptions is a new immutable record that groups all 15 shared reply options (markup,
delivery, callback options). Both PlainReply and LocalizedReply delegate to it
internally — adding a future Telegram Bot API option only requires a new field in
ReplyOptions, not changes to both reply classes.
New extension point
Add your own Telegram Bot API call to the dispatch pipeline without modifying any existing
code — just register a BotReplyAction bean:
@Component
public class NotifySupervisorReplyAction implements BotReplyAction {
@Override
public boolean supports(ReplyOptions options, BotRequest request) {
return options.protectContent() != null && options.protectContent();
}
@Override
public void execute(BotRequest request, BotResponse response,
String resolvedText, ReplyOptions options) {
// send a copy to supervisor chat, log to audit, etc.
}
@Override
public int getOrder() { return 30; }
}
See the Custom Reply Actions guide for full documentation.
ReplyOptions accessor
Both PlainReply and LocalizedReply now expose a getOptions() method returning the
underlying ReplyOptions. This is primarily intended for custom BotReplyAction
implementations and framework-internal use.
Summary Table
| Change | Type | Action required |
|---|---|---|
@BotParseMode annotation on handler methods | New feature | None — additive |
.withParseMode(String) on all MarkupAware types | New feature | None — additive |
easygram.telegram-url.* properties | New feature | None — additive |
BotTelegramUrlProvider SPI | New feature | None — additive |
BotKafkaProducerFactoryProvider SPI | New feature | None — additive |
BotKafkaConsumerFactoryProvider SPI | New feature | None — additive |
BotRabbitConnectionFactoryProvider SPI | New feature | None — additive |
| Topic / exchange properties are now optional | Enhancement | None — defaults unchanged |
BotReplyAction SPI + BotReplyActionChain | New SPI / internal refactor | None — fully additive |
ReplyOptions record grouping shared reply fields | Internal refactor | None — getOptions() exposed for extension |