RSS'den 66 sosyal ağa: Facebook, Instagram, X, LinkedIn, Telegram ve daha fazlası Blog Satış Ortaklığı İletişim
Giriş yapın Ücretsiz başlayın
Updated: 2026-10-20
Micronaut RSS Feed: Build It With Micronaut Data and Auto-Post

Short answer: To serve an RSS feed from a Micronaut application, add a controller with a GET route for /feed.xml, read the newest published items through a Micronaut Data repository, build the RSS 2.0 XML with an escape helper, format dates with the RFC 1123 formatter in UTC, and return the text with the content type application/rss+xml. Add an ETag and a short Cache-Control header so repeat requests get a cheap 304 answer, test the address with curl and a validator, and then give the public URL to an auto-poster so every new item reaches your social channels.

Micronaut is a JVM framework known for fast start-up and low memory use, which makes it a popular choice for small services and serverless functions. Many of those services publish something people want to follow: articles, release notes, listings, event dates or product updates. A feed is the standard way to let readers, aggregators and auto-posters watch that content without an account or an API key. This guide builds a complete Micronaut RSS feed with caching and conditional requests, explains each decision and ends with the checks that keep the feed dependable once other systems depend on it.

When a Micronaut service needs a feed

A feed is worth adding whenever the service publishes things that people outside the application want to follow:

  • articles, news or announcements stored in a database and shown through a separate front end;
  • release notes or a changelog for a public API or library;
  • listings, events or offers added by staff or by users;
  • any content that should also appear on social networks without being copied by hand.

If your content already lives in a blog or a CMS, use the feed it provides instead; see how to find your WordPress feed link. For the same job in another JVM framework, see the Spring Boot guide.

Set up the project

Create a new application with Micronaut Launch or with the Micronaut command line tool. Choose Java and Gradle, then add the Data JDBC feature, a connection pool and the driver for your database. The example below assumes a table named posts with a numeric id, a title, a slug, a short summary, a status and a publication time.

mn create-app com.example.feed --build=gradle --lang=java
cd feed
# add the Data JDBC feature, a JDBC pool and your database driver in build.gradle,
# then describe the connection in src/main/resources/application.yml

Keep the public address of the site in configuration rather than reading it from the incoming request. Behind a proxy, the request may show an internal host name, and a feed full of internal addresses is useless to every reader.

feed:
  site-url: https://www.example.com
  title: Example News
  description: The latest articles from Example News

The entity and the repository

Micronaut Data turns method names into queries at compile time, so a repository interface is all you need to ask for the newest published items. The method name below means: take the first twenty rows with the given status and a publication time that is not in the future, newest first.

package com.example.feed;

import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
import java.time.Instant;
import java.util.List;

@MappedEntity("posts")
public record Post(@Id Long id, String title, String slug,
                   String summary, String status, Instant publishedAt) { }

@JdbcRepository(dialect = Dialect.POSTGRES)
public interface PostRepository extends CrudRepository<Post, Long> {
    List<Post> findTop20ByStatusAndPublishedAtLessThanEqualsOrderByPublishedAtDesc(
            String status, Instant now);
}

The condition on the publication time matters. Without it, a post scheduled for tomorrow would appear in the feed today, and an auto-poster would share it before it exists on the website.

The controller

The controller builds the XML, adds an ETag taken from the content and answers conditional requests. The escape helper handles the five characters that are special in XML. Never insert a title or a summary into the feed without it.

package com.example.feed;

import io.micronaut.context.annotation.Value;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Header;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat;
import java.util.List;
import java.util.Optional;

@Controller
public class FeedController {

    private final PostRepository posts;
    private final String siteUrl;
    private final String title;
    private final String description;

    public FeedController(PostRepository posts,
                          @Value("${feed.site-url}") String siteUrl,
                          @Value("${feed.title}") String title,
                          @Value("${feed.description}") String description) {
        this.posts = posts;
        this.siteUrl = siteUrl;
        this.title = title;
        this.description = description;
    }

    @Get(uri = "/feed.xml", produces = "application/rss+xml")
    public HttpResponse<String> feed(@Header(HttpHeaders.IF_NONE_MATCH) Optional<String> ifNoneMatch)
            throws Exception {
        List<Post> items = posts
                .findTop20ByStatusAndPublishedAtLessThanEqualsOrderByPublishedAtDesc(
                        "published", Instant.now());
        String xml = build(items);
        String etag = "\"" + HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
                .digest(xml.getBytes(StandardCharsets.UTF_8)), 0, 8) + "\"";
        if (ifNoneMatch.filter(etag::equals).isPresent()) {
            return HttpResponse.notModified().header(HttpHeaders.ETAG, etag);
        }
        return HttpResponse.ok(xml)
                .contentType(MediaType.of("application/rss+xml;charset=UTF-8"))
                .header(HttpHeaders.ETAG, etag)
                .header(HttpHeaders.CACHE_CONTROL, "public, max-age=300");
    }

    private String build(List<Post> items) {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
          .append("<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n<channel>\n")
          .append("<title>").append(esc(title)).append("</title>\n")
          .append("<link>").append(esc(siteUrl)).append("/</link>\n")
          .append("<description>").append(esc(description)).append("</description>\n")
          .append("<language>en</language>\n");
        for (Post p : items) {
            String url = siteUrl + "/" + p.slug() + "/";
            sb.append("<item>\n")
              .append("<title>").append(esc(p.title())).append("</title>\n")
              .append("<link>").append(esc(url)).append("</link>\n")
              .append("<guid isPermaLink=\"false\">post-").append(p.id()).append("</guid>\n")
              .append("<pubDate>").append(rfc822(p.publishedAt())).append("</pubDate>\n")
              .append("<description>").append(esc(p.summary())).append("</description>\n")
              .append("</item>\n");
        }
        return sb.append("</channel>\n</rss>\n").toString();
    }

    private static String rfc822(Instant t) {
        return DateTimeFormatter.RFC_1123_DATE_TIME.format(t.atOffset(ZoneOffset.UTC));
    }

    private static String esc(String s) {
        return s == null ? "" : s.replace("&", "&amp;").replace("<", "&lt;")
                .replace(">", "&gt;").replace("\"", "&quot;").replace("'", "&apos;");
    }
}

Run the application and open the route in a browser or with curl. You should see well-formed XML with at most twenty items, newest first.

Where each field comes from

Feed fieldSource in the exampleRule to keep
titleThe title columnPlain text, escaped, no markup
linkSite address plus slugAbsolute address that returns 200 directly
guidThe row id with a prefixNever changes, even if the slug changes
pubDateThe publication time in UTCEnglish day and month names, never in the future
descriptionThe summary columnOne or two sentences that make sense alone

The identifier is the most important decision. An auto-poster remembers which items it has already shared by their GUID. If the value changes when someone edits a slug or republishes an article, the same story is shared again. Using the database id with a fixed prefix avoids that. The problem and its fixes are described in why a feed auto-posts the same article twice.

Dates in the English format

RSS dates follow RFC 822, with English abbreviations for days and months. The RFC 1123 formatter in java.time always produces English names, regardless of the language of the server, and printing the time at the UTC offset ends it with GMT. Do not use a formatter that follows the default locale; a server with a different language setting would print month names that readers cannot parse. If you see date warnings in a validator, read the guide to RSS date format errors.

Conditional requests and caching

Auto-posters, readers and search engines request a feed often, and almost always nothing has changed. The controller above handles that in two ways. It sends an ETag that is a short hash of the finished XML, and when the client returns the same value in the If-None-Match header, the answer is a 304 with no body. It also sends a Cache-Control header that lets a proxy or a content delivery network keep the response for a few minutes. Together they cut the cost of each check to a query and a hash.

If the query is heavy or the traffic is high, keep the finished XML in a small in-memory holder and rebuild it only when a post is published. The background to both techniques is in the guide to ETags and conditional GET ve the guide to feed caching. Do not cache for hours, because a long cache delays every new post by the same amount.

The content type and character encoding

The route is declared with the type application/rss+xml, and the response states UTF-8. Readers and validators accept text/xml as well, but a plain text type or an HTML type can make a browser show the markup or trigger a download. Make sure the XML declaration, the header and the actual bytes agree. A mismatch usually shows up as broken accented letters in titles. More detail is in the guide to the RSS content type header.

Images and richer items

An auto-poster is more useful when each item carries a picture. Add an enclosure element with the address, the byte length and the type of the image, or a media content element if you prefer that namespace. Use an absolute https address and a file of reasonable size. Without an image, many networks show a plain text card. How tools read these elements is explained in the guide to enclosure tags. If the summary contains HTML, wrap it in a CDATA block or escape it, as described in the CDATA guide.

Test the feed before you connect it

  1. Request the route with curl and look at the headers: the status should be 200, the content type should be application/rss+xml and an ETag should be present.
  2. Repeat the request with the ETag in an If-None-Match header and confirm that a 304 comes back.
  3. Paste the public address into the W3C Feed Validation Service and fix every error.
  4. Publish a test row in the database and check that it appears at the top of the feed.
  5. Set a publication time in the future and check that the item stays hidden until then.

There is a longer checklist in the feed validation guide and a short curl routine in the guide to debugging a feed with curl.

Micronaut compared with Spring Boot and Quarkus

The three frameworks solve the same problem in a similar way, and the feed itself is plain XML in all of them. Micronaut does its dependency injection and query generation at compile time, which keeps start-up quick and memory small, and that suits a service that runs briefly in a container or a function. Spring Boot has the largest library of examples and a ready-made feed view class, while Quarkus is close to Micronaut in speed and offers a different programming model. Choose the framework your team already knows. A feed route is small enough that the choice makes almost no difference to readers or to auto-posters.

Common mistakes to avoid

  • Unescaped text. One ampersand in a title can make the whole feed invalid.
  • Future items. Filter by publication time so scheduled content stays hidden until its moment.
  • Unstable GUIDs. Use an identifier that never changes.
  • Addresses taken from the request. Build links from the configured public address.
  • Too many items. Ten to twenty of the newest items are enough, and a smaller file downloads and parses faster.
  • Changing the feed address. Keep the route, or redirect the old one permanently; see what to check when a feed stops updating.

How PostRSS works with a Micronaut feed

PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed route, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new item, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. Keyword filters, UTM parameters and posting windows let you tune each channel, and webhooks connect PostRSS to your own systems. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.

İlgili Okumalar

The bottom line

Micronaut needs only a repository method, a controller and a small escape helper to serve a dependable RSS feed: select published items dated in the past, escape every value, use a stable GUID, format dates in English UTC, build links from a configured public address and return the XML with the right content type, an ETag and a short cache. Test the route with curl and a validator, keep the address unchanged and connect it to an auto-poster. After that, every item you publish can reach your social channels automatically.

FAQ

Does Micronaut have a built-in RSS generator?

No. Micronaut has no feed support in its core. You build the XML yourself or use a library such as ROME, and the controller returns the text with the right content type.

Why add an ETag to the feed response?

Auto-posters and readers check feeds very often. When the ETag has not changed, they receive a 304 answer without a body, which saves bandwidth and processing on both sides.

Can I use ROME instead of building the XML by hand?

Yes. ROME builds RSS and Atom from Java objects and handles escaping for you. The rules stay the same: only published items, newest first, a limit of about twenty and a stable GUID for each item.

How many items should the feed contain?

Between ten and twenty of the newest published items is enough. An auto-poster only needs recent items, and a smaller file is faster to download and parse.

How do I stop old posts from being shared again?

Give every item a stable identifier and use it as the GUID. Avoid editing the publication date of items that are already public, because some tools treat a changed date as a new item.

Can I use this feed with PostRSS?

Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new items are shared as soon as they appear.

New guides, once a month

What changed in the networks, what broke, and how to fix it before it costs you reach.

We send a confirmation e-mail first. Unsubscribe any time.

Ekibimizden daha fazla araç

PostRSS'in arkasındaki ekip Internet Solutions tarafından geliştirildi. Her biri size farklı bir şekilde zaman kazandırır.