
Short answer: A Spring Boot RSS feed is one controller method. Add the Rome library, build a SyndFeed of type rss_2.0 from your latest published posts with absolute URLs and real publication dates, write it with SyndFeedOutput, and return it as application/rss+xml with a short cache header. Validate it once and any RSS auto-poster can share every new article automatically.
Spring Boot sits behind a huge number of Java web applications: corporate websites, customer portals, documentation sites, news services and blogs built on a custom data model. Spring gives you excellent building blocks but no finished feed, so the feed is often forgotten. That costs reach. A feed is how readers subscribe, how newsletters and aggregators pick up your content, and how social automation tools publish each new article without anyone copying links by hand. This guide shows a dependable way to build the feed, the problems that most often break Java feeds, and how to connect the result to social auto-posting.
Before writing code, fix the requirements. A reliable feed:
Images and categories are optional extras that become important once the feed drives social posts. We add them after the basic version works.
Rome is the long-standing Java library for reading and writing RSS and Atom. It models a feed as Java objects and takes care of XML escaping and date formatting, which is exactly where hand-built feeds tend to fail. Add the com.rometools:rome dependency to your Maven or Gradle build, using the current version listed in the Rome project repository.
Rome offers two models. The SyndFeed model is format-neutral: you describe the feed once and choose the output type, such as rss_2.0 arba atom_1.0, when writing. The lower-level WireFeed model maps directly to one format. For most sites the neutral model is simpler and lets you publish RSS and Atom from the same code.
The example assumes a JPA entity Post with title, slug, summary, publishedAt (an Instant) and published, and a Spring Data repository. The public base URL comes from configuration, which is the most predictable way to get correct absolute links.
@RestController
public class FeedController {
private final PostRepository posts;
private final String baseUrl;
public FeedController(PostRepository posts,
@Value("${app.base-url}") String baseUrl) {
this.posts = posts;
this.baseUrl = baseUrl.replaceAll("/$", "");
}
@GetMapping(value = "/rss.xml", produces = "application/rss+xml;charset=UTF-8")
public ResponseEntity<String> rss() throws FeedException {
SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("rss_2.0");
feed.setTitle("Example Blog");
feed.setLink(baseUrl + "/");
feed.setDescription("New articles from Example");
feed.setLanguage("en");
List<SyndEntry> entries = new ArrayList<>();
for (Post p : posts.findTop30ByPublishedTrueAndPublishedAtBeforeOrderByPublishedAtDesc(Instant.now())) {
String url = baseUrl + "/blog/" + p.getSlug();
SyndEntry e = new SyndEntryImpl();
e.setTitle(p.getTitle());
e.setLink(url);
e.setUri(url);
e.setPublishedDate(Date.from(p.getPublishedAt()));
SyndContent d = new SyndContentImpl();
d.setType("text/plain");
d.setValue(p.getSummary());
e.setDescription(d);
entries.add(e);
}
feed.setEntries(entries);
String xml = new SyndFeedOutput().outputString(feed);
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES).cachePublic())
.body(xml);
}
}Several details matter. The derived query method returns only published posts whose publication time has passed, newest first, limited to thirty, so drafts and scheduled posts stay out of the feed. The entry URI is the permanent article URL; in RSS 2.0 output Rome writes it as the item’s guid, and it should never change after publication. The produces attribute sets the content type, and the cache header lets proxies and clients reuse the response for a few minutes.
Java has two classic sources of feed bugs: dates and character encoding.
LocalDateTime has no time zone, so converting it to a Date silently uses the server’s zone. Store publication times as Instant arba OffsetDateTime and convert with Date.from(instant). Rome then writes the RFC 822 date that RSS requires.charset=UTF-8 in produces makes the response header match the XML declaration.text/html so Rome escapes it properly. Control characters copied from word processors are invalid in XML and should be removed.For a closer look at the date format itself, see our guide to RSS date format and RFC 822 errors, and for broken characters in posts, the article on encoding issues in RSS feeds.
The example takes the base URL from app.base-url in configuration. If you prefer to build links from the current request, for example with ServletUriComponentsBuilder.fromCurrentContextPath(), remember that behind Nginx, a load balancer or Kubernetes ingress the application may see an internal scheme, host or port.
server.forward-headers-strategy to native arba framework so Spring Boot applies X-Forwarded-Proto ir X-Forwarded-Host from your proxy.http://, localhost, :8080 and internal host names. None should appear.Relative links cause similar trouble. They work in Thymeleaf pages but break once an item is read elsewhere, as explained in our article on relative URLs and broken links in RSS feeds.
Richer items produce better social posts. When the basic feed validates, add:
SyndEnclosure with an absolute image URL, a MIME type such as image/jpeg and the file length, and attach it with setEnclosures. Use a mid-sized image; many auto-posting tools take it as the post picture.SyndCategory objects for tags or sections. Tools can turn them into hashtags or use them for filtering.og:title, og:description ir og:image, because tools often fall back to them when the feed has no image.<link rel="alternate" type="application/rss+xml" title="Example Blog" href="https://example.com/rss.xml"> so browsers and tools can find the feed from any page.Because the SyndFeed model is format-neutral, offering Atom as well costs almost nothing: move the feed-building code into a service, call it from a second endpoint such as /atom.xml, and set the feed type to atom_1.0 with the application/atom+xml content type. Some readers prefer Atom, while most auto-posting tools accept either.
The same service can produce category feeds. Map /category/{slug}/rss.xml, look up the category, return 404 when it does not exist, and pass the filtered posts to the builder with a category-specific title. Keep each article’s GUID identical in every feed it appears in, so tools that read several feeds see one item, not two. If duplicates ever appear, the guide on duplicate GUID fixes explains how auto-posters decide what is new.
Feeds are polled constantly by readers, aggregators and auto-posting services, often every few minutes. Every uncached request means a database query and XML generation.
ShallowEtagHeaderFilter computes an ETag from the response body and answers 304 when the client already has the same version. Alternatively, set lastModified to the newest item’s date and use WebRequest.checkNotModified() to skip building the feed entirely.@Cacheable service method can hold the generated XML, evicted with @CacheEvict when a post is published.Keep lifetimes short, because long caches delay auto-posting. Our explanation of why cached feeds make auto-posts lag shows how the delays add up.
A MockMvc test protects the feed from regressions. Perform a GET on /rss.xml, expect status 200 and a content type compatible with application/rss+xml, then parse the body with Rome’s SyndFeedInput and assert that the first entry has a title, an absolute link and a publication date. Run it in CI and a broken template or a dropped filter is caught before release.
| Problem | Typical cause in Spring Boot | Fix |
|---|---|---|
| Links use http or port 8080 | Forwarded headers ignored | Configured base URL or forward-headers-strategy |
| Dates shifted by hours | LocalDateTime converted with server zone | Store Instant and use Date.from |
| Broken characters | Non-UTF-8 connection or response | UTF-8 everywhere and charset in produces |
| Feed returned as JSON or text | Missing produces attribute | Set application/rss+xml on the mapping |
| Same post shared twice | Entry URI built from an editable title | Use the permanent URL or ID |
| Scheduled posts visible early | Query ignores publishedAt | Filter on publishedAt before now |
Before connecting automation, fetch the headers with curl -I, expect HTTP 200 without redirects, run the URL through the W3C Feed Validation Service and open a few item links in a private window to confirm they are public.
With a valid feed, sharing new articles no longer depends on someone remembering to post. Add the feed URL to an auto-posting service, connect your social accounts and channels, decide how posts are built from the title, summary and link, and choose posting hours if needed. The service checks the feed and publishes each new item once.
Java teams often route content by audience: company announcements to a professional network, engineering articles to Mastodon or Bluesky, release notes to a Telegram channel, a Discord server or a team chat. Category feeds make each route a matter of choosing the right feed. More setups are collected under technical guides and the RSS kanalas topic.
PostRSS reads any RSS or Atom feed, including one generated with Rome, and publishes each new item to the networks you connect. It checks feeds as often as every minute, takes the image from the feed item or the page’s Open Graph tags, and offers keyword filters, hashtags from item categories and UTM parameters. Developers can also use the PostRSS REST API v1 and outgoing webhooks, and the service has been running since 2014. See the features page for all 66 networks and the pricing page for what each plan includes.
A Spring Boot RSS feed needs one controller and a few careful choices: Rome for safe XML, a configured HTTPS base URL, instants instead of local times, permanent entry URIs, UTF-8 throughout, the right content type and a short cache. Add images and categories, offer Atom from the same code if you like, cover the endpoint with a MockMvc test and validate it once. After that the feed runs quietly in the background, and every new article reaches subscribers and your social channels without manual sharing.
Spring Boot has no feed starter, but adding the Rome library and one controller method is enough to publish a valid RSS 2.0 or Atom feed.
In RSS 2.0 output, Rome writes the entry URI as the guid. Set it to the permanent article URL or another value that never changes after publication.
The publication time was probably stored as a LocalDateTime and converted with the server’s time zone. Store an Instant and convert it with Date.from.
Yes. Rome’s SyndFeed model is format-neutral, so one builder can write rss_2.0 for one endpoint and atom_1.0 for another.
Yes. Once the feed is public and valid, an RSS auto-posting service can watch it and publish each new article to the social networks and channels you connect.
What changed in the networks, what broke, and how to fix it before it costs you reach.
Sukūrė Internet Solutions – PostRSS komanda. Kiekvienas produktas sutaupo laiko savaip.