This example shows how to use the ROME library to generate an Atom feed in a Spring MVC application.
Deprecation: As of Spring Framework 7.0, the org.springframework.web.servlet.view.feed package is deprecated. This includes AbstractAtomFeedView, AbstractRssFeedView and AbstractFeedView. The classes are still present, but Spring's reference documentation now suggests that libraries provide their own View types or that applications render the feed directly in a web handler.
Example
Additional Maven Dependency
pom.xml<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>2.1.0</version>
</dependency>
Java Config class
package com.logicbig.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@EnableWebMvc
@Configuration
@ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(0);
registry.viewResolver(resolver);
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setOrder(1);
registry.viewResolver(viewResolver);
}
}
In the above configuration, we are using BeanNameViewResolver to resolve the Atom view by its bean name. Because InternalResourceViewResolver accepts any view name, the BeanNameViewResolver must be consulted first. It returns null when no bean matches, which lets the next resolver try. Declaring it first works, but setting an explicit order on both resolvers is more robust.
Implementing the View
Spring provides the AbstractAtomFeedView support class for generating an Atom feed with ROME. We extend it and implement buildFeedEntries() (required). We can also override buildFeedMetadata() to set feed-level information such as the title.
package com.logicbig.example;
import com.rometools.rome.feed.atom.Content;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
import com.rometools.rome.feed.atom.Link;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.feed.AbstractAtomFeedView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Component("atomFeedView")
public class MyAtomFeedView extends AbstractAtomFeedView {
@Override
protected void buildFeedMetadata(Map<String, Object> model, Feed feed,
HttpServletRequest request) {
FeedInfo feedInfo = (FeedInfo) model.get("feedInfo");
feed.setTitle(feedInfo.getTitle());
}
@Override
protected List<Entry> buildFeedEntries(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
List<Entry> entries = new ArrayList<>();
FeedInfo feedInfo = (FeedInfo) model.get("feedInfo");
for (FeedEntry fe : feedInfo.getFeedEntries()) {
Entry entry = new Entry();
entry.setTitle(fe.getTitle());
Link link = new Link();
link.setHref(fe.getLink());
entry.setAlternateLinks(Arrays.asList(link));
entry.setPublished(fe.getDate());
Content content = new Content();
content.setValue(fe.getContent());
entry.setSummary(content);
entries.add(entry);
}
return entries;
}
}
package com.logicbig.example;
import java.util.ArrayList;
import java.util.List;
public class FeedInfo {
private String title;
private String link;
private String desc;
private List<FeedEntry> feedEntries = new ArrayList<>();
.............
}
package com.logicbig.example;
import java.util.Date;
public class FeedEntry {
String title;
String link;
String content;
Date date;
.............
}
Note that RFC 4287 requires every Atom feed and every entry to have an id, a title and an updated timestamp. To keep the example short, we only set a subset of these. For a production feed, also call Feed.setId(), Feed.setUpdated(), Entry.setId() and Entry.setUpdated().
A Spring Controller
package com.logicbig.example;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/")
public class AtomFeedController {
@GetMapping("/")
public String mainView() {
return "main";
}
@GetMapping("/atomFeed")
public String handleForexRequest(Model model) {
model.addAttribute("feedInfo", getFeedInfo());
return "atomFeedView";
}
private static FeedInfo getFeedInfo() {
FeedInfo feedInfo = new FeedInfo();
feedInfo.setTitle("Logicbig Tutorials");
feedInfo.setLink("http://www.logicbig.com");
feedInfo.setDesc("Logicbig tutorials feed");
List<FeedEntry> list = new ArrayList<>();
list.add(createFeedEntry("Spring Framework", "spring-framework",
"Spring Framework Tutorials", new Date()));
list.add(createFeedEntry("Java EE", "java-ee-tutorial",
"Java EE Tutorials", new Date()));
list.add(createFeedEntry("Java Core", "/core-java-tutorial/",
"Core Java Tutorials", new Date()));
feedInfo.setFeedEntries(list);
return feedInfo;
}
private static FeedEntry createFeedEntry(String title,
String uri,
String content,
Date date) {
FeedEntry fe = new FeedEntry();
fe.setTitle(title);
fe.setLink("http://www.logicbig.com/tutorials/" + uri);
fe.setContent(content);
fe.setDate(date);
return fe;
}
}
src/main/webapp/WEB-INF/views/main.jsp<html>
<body>
<h2>Spring Atom Feed Example</h2>
<a href="/atomFeed">Tutorials Feed</a>
</body>
</html>
Running the example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
Output
On clicking the 'Tutorials Feed' link:
The response is served with the application/atom+xml content type, so a browser may show it as raw XML or offer to open it in a feed reader.
$ curl -s "http://localhost:8080/atomFeed" <?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>Logicbig Tutorials</title> <entry> <title>Spring Framework</title> <link rel="alternate" href="http://www.logicbig.com/tutorials/spring-framework" /> <published>2026-10-06T13:02:48Z</published> <summary>Spring Framework Tutorials</summary> </entry> <entry> <title>Java EE</title> <link rel="alternate" href="http://www.logicbig.com/tutorials/java-ee-tutorial" /> <published>2026-10-06T13:02:48Z</published> <summary>Java EE Tutorials</summary> </entry> <entry> <title>Java Core</title> <link rel="alternate" href="http://www.logicbig.com/tutorials//core-java-tutorial/" /> <published>2026-10-06T13:02:48Z</published> <summary>Core Java Tutorials</summary> </entry> </feed>
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 4.1.0.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- rome 2.1.0 (All Roads Lead to ROME. ROME is a set of Atom/RSS Java utilities that make it
easy to work in Java with most syndication formats. Today it accepts all flavors of RSS
(0.90, 0.91, 0.92, 0.93, 0.94, 1.0 and 2.0), Atom 0.3 and Atom 1.0 feeds. Rome includes
a set of parsers and generators for the various flavors of feeds, as well as converters
to convert from one format to another. The parsers can give you back Java objects that
are either specific for the format you want to work with, or a generic normalized
SyndFeed object that lets you work on with the data without bothering about the
underlying format. [Description from rome-2.1.0.pom])
- JDK 25
- Maven 3.9.11
|