> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Spring

> Build content-driven Spring Boot apps with ButterCMS. Covers setup, pages, collections, components, and blog content with Spring MVC.

## 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](../../core-concepts/content-types/pages), [Collections](../../core-concepts/content-types/collections), and [Blog Posts](../../core-concepts/content-types/blog-engine).

<Tip>
  In order for the snippets to work, you'll need to [setup your dashboard content schemas inside of ButterCMS](../buttercms-setup) first.
</Tip>

### 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.

<Card title="Spring Starter Project" icon="rocket" href="../starter-projects/spring">
  Hit the ground running with a pre-configured Spring + ButterCMS setup.
</Card>

## Installation

<Tabs>
  <Tab title="Maven">
    ```xml theme={null}
    <dependency>
        <groupId>com.buttercms</groupId>
        <artifactId>buttercmsclient</artifactId>
        <version>1.13.0</version>
    </dependency>
    ```
  </Tab>

  <Tab title="Gradle (Groovy)">
    ```groovy theme={null}
    implementation 'com.buttercms:buttercmsclient:1.13.0'
    ```
  </Tab>

  <Tab title="Gradle (Kotlin)">
    ```kotlin theme={null}
    implementation("com.buttercms:buttercmsclient:1.13.0")
    ```
  </Tab>
</Tabs>

## Configuration

```java theme={null}
// 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);
    }
}
```

```properties theme={null}
# application.properties
buttercms.api.token=${BUTTERCMS_API_TOKEN}
```

<Info>
  For complete SDK documentation including all available methods and configuration options, see the [Java SDK Reference](../sdks/java-sdk).
</Info>

## Service layer

<Tabs>
  <Tab title="Standard Service">
    ```java theme={null}
    // 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();
        }
    }
    ```
  </Tab>

  <Tab title="Cached Service">
    ```java theme={null}
    // 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
        }
    }
    ```
  </Tab>
</Tabs>

## Pages

<Tabs>
  <Tab title="MVC Controller">
    ```java theme={null}
    // 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";
        }
    }
    ```

    ```html theme={null}
    <!-- 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>
    ```
  </Tab>

  <Tab title="REST Controller">
    ```java theme={null}
    // 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);
        }
    }
    ```
  </Tab>
</Tabs>

## Collections

<Tabs>
  <Tab title="MVC Controller">
    ```java theme={null}
    // 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";
        }
    }
    ```

    ```html theme={null}
    <!-- 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>
    ```
  </Tab>

  <Tab title="REST Controller">
    ```java theme={null}
    // 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);
        }
    }
    ```
  </Tab>
</Tabs>

## Dynamic components

### Component renderer

```java theme={null}
// 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

<Tabs>
  <Tab title="MVC Controller">
    ```java theme={null}
    // 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";
            }
        }
    }
    ```

    ```html theme={null}
    <!-- src/main/resources/templates/component-page.html -->
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <body>
        <main th:utext="${components}"></main>
    </body>
    </html>
    ```
  </Tab>

  <Tab title="REST Controller">
    ```java theme={null}
    @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();
            }
        }
    }
    ```
  </Tab>
</Tabs>

## Blog

<Tabs>
  <Tab title="Blog List">
    <Tabs>
      <Tab title="MVC Controller">
        ```java theme={null}
        // 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";
            }
        }
        ```

        ```html theme={null}
        <!-- 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>
        ```
      </Tab>

      <Tab title="REST Controller">
        ```java theme={null}
        @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);
            }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Blog Post">
    <Tabs>
      <Tab title="MVC Controller">
        ```java theme={null}
        @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";
            }
        }
        ```

        ```html theme={null}
        <!-- 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>
        ```
      </Tab>

      <Tab title="REST Controller">
        ```java theme={null}
        @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();
            }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Caching configuration

```java theme={null}
// 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

```java theme={null}
// 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

```html theme={null}
<!-- 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

<CardGroup cols={2}>
  <Card title="Java SDK" icon="java" href="../sdks/java-sdk">
    Complete SDK reference
  </Card>

  <Card title="GitHub Repository" icon="github" href="https://github.com/ButterCMS/buttercms-java">
    View source code
  </Card>

  <Card title="Webhooks" icon="bell" href="../../api/concepts/webhooks-overview">
    Set up content webhooks
  </Card>
</CardGroup>
