Skip to main content

Overview

This integration guide shows you how to how to update your existing project to:
  1. install the ButterCMS package
  2. instantiate ButterCMS
  3. 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

<dependency>
    <groupId>com.buttercms</groupId>
    <artifactId>buttercmsclient</artifactId>
    <version>1.13.0</version>
</dependency>

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

// 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();
    }
}

Pages

// 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>

Collections

// 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>

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("&", "&amp;")
                   .replace("<", "&lt;")
                   .replace(">", "&gt;")
                   .replace("\"", "&quot;");
    }
}

Using in pages

// 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>

Blog

// 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>

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