Overview
This integration guide shows you how to how to update your existing project to:- install the ButterCMS package
- instantiate ButterCMS
- create components to fetch and display each of the three ButterCMS content types: Pages, Collections, and Blog Posts.
In order for the snippets to work, you’ll need to setup your dashboard content schemas inside of ButterCMS first.
Starter project
Or, you can jump directly to the starter project below, which will allow you to clone, install, run, and deploy a fully working starter project that’s integrated with content already inside of your ButterCMS account.Spring Starter Project
Hit the ground running with a pre-configured Spring + ButterCMS setup.
Installation
- Maven
- Gradle (Groovy)
- Gradle (Kotlin)
<dependency>
<groupId>com.buttercms</groupId>
<artifactId>buttercmsclient</artifactId>
<version>1.13.0</version>
</dependency>
implementation 'com.buttercms:buttercmsclient:1.13.0'
implementation("com.buttercms:buttercmsclient:1.13.0")
Configuration
// src/main/java/com/example/config/ButterCMSConfig.java
package com.example.config;
import com.buttercms.ButterCMSClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ButterCMSConfig {
@Value("${buttercms.api.token}")
private String apiToken;
@Bean
public ButterCMSClient butterCMSClient() {
return new ButterCMSClient(apiToken);
}
}
# application.properties
buttercms.api.token=${BUTTERCMS_API_TOKEN}
For complete SDK documentation including all available methods and configuration options, see the Java SDK Reference.
Service layer
- Standard Service
- Cached Service
// src/main/java/com/example/service/ButterCMSService.java
package com.example.service;
import com.buttercms.ButterCMSClient;
import com.buttercms.model.*;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class ButterCMSService {
private final ButterCMSClient client;
public ButterCMSService(ButterCMSClient client) {
this.client = client;
}
public Page getPage(String pageType, String slug) {
return client.getPage(pageType, slug, null).getData();
}
public Page getPage(String pageType, String slug, Map<String, String> params) {
return client.getPage(pageType, slug, params).getData();
}
public List<Page> getPages(String pageType) {
return client.getPages(pageType, null).getData();
}
public Content getContent(String... keys) {
return client.getContent(Arrays.asList(keys), null).getData();
}
public PostsResponse getPosts(int page, int pageSize) {
Map<String, String> params = new HashMap<>();
params.put("page", String.valueOf(page));
params.put("page_size", String.valueOf(pageSize));
return client.getPosts(params);
}
public PostsResponse getPostsByCategory(String categorySlug, int page, int pageSize) {
Map<String, String> params = new HashMap<>();
params.put("category_slug", categorySlug);
params.put("page", String.valueOf(page));
params.put("page_size", String.valueOf(pageSize));
return client.getPosts(params);
}
public Post getPost(String slug) {
return client.getPost(slug).getData();
}
public List<Post> searchPosts(String query) {
Map<String, String> params = new HashMap<>();
params.put("query", query);
return client.searchPosts(params).getData();
}
}
// src/main/java/com/example/service/CachedButterCMSService.java
package com.example.service;
import com.buttercms.ButterCMSClient;
import com.buttercms.model.*;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class CachedButterCMSService {
private final ButterCMSClient client;
public CachedButterCMSService(ButterCMSClient client) {
this.client = client;
}
@Cacheable(value = "pages", key = "#pageType + '-' + #slug")
public Page getPage(String pageType, String slug) {
return client.getPage(pageType, slug, null).getData();
}
public Page getPagePreview(String pageType, String slug) {
// Skip cache for preview
return client.getPage(pageType, slug, Map.of("preview", "1")).getData();
}
@Cacheable(value = "posts", key = "'list-' + #page + '-' + #pageSize")
public PostsResponse getPosts(int page, int pageSize) {
Map<String, String> params = Map.of(
"page", String.valueOf(page),
"page_size", String.valueOf(pageSize)
);
return client.getPosts(params);
}
@Cacheable(value = "post", key = "#slug")
public Post getPost(String slug) {
return client.getPost(slug).getData();
}
@CacheEvict(value = {"pages", "posts", "post"}, allEntries = true)
public void clearCache() {
// Cache cleared
}
}
Pages
- MVC Controller
- REST Controller
// src/main/java/com/example/controller/PageController.java
package com.example.controller;
import com.example.service.ButterCMSService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class PageController {
private final ButterCMSService butterService;
public PageController(ButterCMSService butterService) {
this.butterService = butterService;
}
@GetMapping("/")
public String home(Model model) {
return showPage("home", false, model);
}
@GetMapping("/page/{slug}")
public String showPage(
@PathVariable String slug,
@RequestParam(defaultValue = "false") boolean preview,
Model model) {
try {
var page = butterService.getPage("landing-page", slug);
model.addAttribute("page", page);
model.addAttribute("isPreview", preview);
return "landing";
} catch (Exception e) {
return "error/404";
}
}
@GetMapping("/pages")
public String listPages(Model model) {
var pages = butterService.getPages("landing-page");
model.addAttribute("pages", pages);
return "page-list";
}
}
<!-- src/main/resources/templates/landing.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${page.fields.seo?.title ?: page.fields.headline}">Page</title>
<meta th:if="${page.fields.seo?.description}"
name="description" th:content="${page.fields.seo.description}">
</head>
<body>
<div th:if="${isPreview}" class="preview-banner">Preview Mode</div>
<main>
<h1 th:text="${page.fields.headline}">Headline</h1>
<p th:text="${page.fields.subheadline}">Subheadline</p>
<img th:if="${page.fields.heroImage}"
th:src="${page.fields.heroImage}"
th:alt="${page.fields.headline}" />
<div th:utext="${page.fields.body}">Body</div>
</main>
</body>
</html>
// src/main/java/com/example/controller/PageApiController.java
package com.example.controller;
import com.example.service.ButterCMSService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/pages")
public class PageApiController {
private final ButterCMSService butterService;
public PageApiController(ButterCMSService butterService) {
this.butterService = butterService;
}
@GetMapping("/{type}/{slug}")
public ResponseEntity<?> getPage(
@PathVariable String type,
@PathVariable String slug) {
try {
var page = butterService.getPage(type, slug);
return ResponseEntity.ok(page);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/{type}")
public ResponseEntity<?> listPages(@PathVariable String type) {
var pages = butterService.getPages(type);
return ResponseEntity.ok(pages);
}
}
Collections
- MVC Controller
- REST Controller
// src/main/java/com/example/controller/CollectionController.java
package com.example.controller;
import com.example.service.ButterCMSService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class CollectionController {
private final ButterCMSService butterService;
public CollectionController(ButterCMSService butterService) {
this.butterService = butterService;
}
@GetMapping("/brands")
public String brands(Model model) {
var content = butterService.getContent("brands");
model.addAttribute("brands", content.getBrands());
return "brands";
}
}
<!-- src/main/resources/templates/brands.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<main>
<h1>Our Brands</h1>
<ul>
<li th:each="brand : ${brands}">
<img th:src="${brand.logo}" th:alt="${brand.name}" />
<h2 th:text="${brand.name}">Brand Name</h2>
<div th:utext="${brand.description}">Description</div>
</li>
</ul>
</main>
</body>
</html>
// src/main/java/com/example/controller/CollectionApiController.java
@RestController
@RequestMapping("/api/collections")
public class CollectionApiController {
private final ButterCMSService butterService;
public CollectionApiController(ButterCMSService butterService) {
this.butterService = butterService;
}
@GetMapping
public ResponseEntity<?> getContent(@RequestParam String[] keys) {
var content = butterService.getContent(keys);
return ResponseEntity.ok(content);
}
}
Dynamic components
Component renderer
// src/main/java/com/example/component/ComponentRenderer.java
package com.example.component;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class ComponentRenderer {
public String render(List<Map<String, Object>> components) {
StringBuilder html = new StringBuilder();
for (Map<String, Object> component : components) {
html.append(renderComponent(component));
}
return html.toString();
}
private String renderComponent(Map<String, Object> component) {
String type = (String) component.get("type");
@SuppressWarnings("unchecked")
Map<String, Object> fields = (Map<String, Object>) component.get("fields");
return switch (type) {
case "hero" -> renderHero(fields);
case "features" -> renderFeatures(fields);
case "cta" -> renderCTA(fields);
default -> "";
};
}
private String renderHero(Map<String, Object> fields) {
String headline = (String) fields.getOrDefault("headline", "");
String subheadline = (String) fields.getOrDefault("subheadline", "");
String image = (String) fields.get("image");
String buttonLabel = (String) fields.get("button_label");
String buttonUrl = (String) fields.get("button_url");
StringBuilder html = new StringBuilder();
html.append("<section class=\"hero\">");
html.append("<h1>").append(escapeHtml(headline)).append("</h1>");
html.append("<p>").append(escapeHtml(subheadline)).append("</p>");
if (buttonLabel != null) {
html.append("<a href=\"").append(escapeHtml(buttonUrl))
.append("\" class=\"btn\">").append(escapeHtml(buttonLabel)).append("</a>");
}
if (image != null) {
html.append("<img src=\"").append(image)
.append("\" alt=\"").append(escapeHtml(headline)).append("\" />");
}
html.append("</section>");
return html.toString();
}
private String renderFeatures(Map<String, Object> fields) {
// Similar implementation for features
return "";
}
private String renderCTA(Map<String, Object> fields) {
// Similar implementation for CTA
return "";
}
private String escapeHtml(String text) {
if (text == null) return "";
return text.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """);
}
}
Using in pages
- MVC Controller
- REST Controller
// src/main/java/com/example/controller/ComponentPageController.java
@Controller
public class ComponentPageController {
private final ButterCMSService butterService;
private final ComponentRenderer componentRenderer;
public ComponentPageController(
ButterCMSService butterService,
ComponentRenderer componentRenderer) {
this.butterService = butterService;
this.componentRenderer = componentRenderer;
}
@GetMapping("/landing/{slug}")
public String componentPage(@PathVariable String slug, Model model) {
try {
var page = butterService.getPage("landing-page", slug);
@SuppressWarnings("unchecked")
var components = (List<Map<String, Object>>) page.getFields().get("body");
var renderedComponents = componentRenderer.render(components != null ? components : List.of());
model.addAttribute("page", page);
model.addAttribute("components", renderedComponents);
return "component-page";
} catch (Exception e) {
return "error/404";
}
}
}
<!-- src/main/resources/templates/component-page.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<main th:utext="${components}"></main>
</body>
</html>
@RestController
@RequestMapping("/api/landing")
public class ComponentPageApiController {
private final ButterCMSService butterService;
@GetMapping("/{slug}")
public ResponseEntity<?> getComponentPage(@PathVariable String slug) {
try {
var page = butterService.getPage("landing-page", slug);
return ResponseEntity.ok(page);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
}
Blog
- Blog List
- Blog Post
- MVC Controller
- REST Controller
// src/main/java/com/example/controller/BlogController.java
@Controller
@RequestMapping("/blog")
public class BlogController {
private final ButterCMSService butterService;
public BlogController(ButterCMSService butterService) {
this.butterService = butterService;
}
@GetMapping
public String listPosts(
@RequestParam(defaultValue = "1") int page,
Model model) {
var response = butterService.getPosts(page, 10);
model.addAttribute("posts", response.getData());
model.addAttribute("meta", response.getMeta());
return "blog/list";
}
@GetMapping("/category/{slug}")
public String postsByCategory(
@PathVariable String slug,
@RequestParam(defaultValue = "1") int page,
Model model) {
var response = butterService.getPostsByCategory(slug, page, 10);
model.addAttribute("posts", response.getData());
model.addAttribute("meta", response.getMeta());
return "blog/list";
}
}
<!-- src/main/resources/templates/blog/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<main>
<h1>Blog</h1>
<ul>
<li th:each="post : ${posts}">
<h2>
<a th:href="@{/blog/{slug}(slug=${post.slug})}"
th:text="${post.title}">Title</a>
</h2>
<p class="meta">
By <span th:text="${post.author.firstName}"></span>
<span th:text="${post.author.lastName}"></span>
</p>
<p th:text="${post.summary}">Summary</p>
</li>
</ul>
<div th:if="${meta.nextPage != null}">
<a th:href="@{/blog(page=${meta.nextPage})}">Next Page</a>
</div>
</main>
</body>
</html>
@RestController
@RequestMapping("/api/posts")
public class BlogApiController {
private final ButterCMSService butterService;
@GetMapping
public ResponseEntity<?> getPosts(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int pageSize) {
var response = butterService.getPosts(page, pageSize);
return ResponseEntity.ok(response);
}
}
- MVC Controller
- REST Controller
@GetMapping("/{slug}")
public String showPost(@PathVariable String slug, Model model) {
try {
var post = butterService.getPost(slug);
model.addAttribute("post", post);
return "blog/post";
} catch (Exception e) {
return "error/404";
}
}
<!-- src/main/resources/templates/blog/post.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${post.seoTitle ?: post.title}">Post</title>
<meta th:if="${post.metaDescription}"
name="description" th:content="${post.metaDescription}">
</head>
<body>
<article>
<h1 th:text="${post.title}">Title</h1>
<p class="meta">
By <span th:text="${post.author.firstName}"></span>
<span th:text="${post.author.lastName}"></span>
</p>
<img th:if="${post.featuredImage}"
th:src="${post.featuredImage}"
th:alt="${post.title}" />
<div th:utext="${post.body}">Body</div>
<a th:href="@{/blog}">Back to Posts</a>
</article>
</body>
</html>
@GetMapping("/{slug}")
public ResponseEntity<?> getPost(@PathVariable String slug) {
try {
var post = butterService.getPost(slug);
return ResponseEntity.ok(post);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
Caching configuration
// src/main/java/com/example/config/CacheConfig.java
package com.example.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
// Uses default Spring cache or configure Redis/Caffeine
}
Webhook cache invalidation
// src/main/java/com/example/controller/WebhookController.java
@RestController
@RequestMapping("/webhooks")
public class WebhookController {
private final CachedButterCMSService butterService;
public WebhookController(CachedButterCMSService butterService) {
this.butterService = butterService;
}
@PostMapping("/buttercms")
public ResponseEntity<?> handleWebhook(@RequestBody Map<String, Object> payload) {
@SuppressWarnings("unchecked")
Map<String, Object> webhook = (Map<String, Object>) payload.get("webhook");
String event = webhook != null ? (String) webhook.get("event") : "";
if (event != null && (event.contains("published") ||
event.contains("updated") || event.contains("deleted"))) {
butterService.clearCache();
}
return ResponseEntity.ok().build();
}
}
SEO
<!-- src/main/resources/templates/fragments/seo.html -->
<head th:fragment="seo(page)">
<title th:text="${page.fields.seo?.title ?: page.fields.headline ?: 'My Site'}">Title</title>
<meta th:if="${page.fields.seo?.description}"
name="description" th:content="${page.fields.seo.description}">
<!-- Open Graph -->
<meta property="og:title"
th:content="${page.fields.seo?.ogTitle ?: page.fields.seo?.title ?: page.fields.headline}">
<meta property="og:description"
th:content="${page.fields.seo?.ogDescription ?: page.fields.seo?.description}">
<meta th:if="${page.fields.seo?.ogImage}"
property="og:image" th:content="${page.fields.seo.ogImage}">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title"
th:content="${page.fields.seo?.title ?: page.fields.headline}">
</head>
Resources
Java SDK
Complete SDK reference
GitHub Repository
View source code
Webhooks
Set up content webhooks