Prismic.io is a headless CMS (Content Management System) that provides content as a service. This Java implementation demonstrates how to integrate with Prismic.io for content management, delivery, and synchronization.
Prismic.io Overview
Key features:
- Content repository management
- Structured content with custom types
- Multi-language support
- Content preview and versioning
- GraphQL and REST APIs
- Webhook integrations
Dependencies and Setup
Maven Configuration:
<dependencies> <!-- Prismic.io Java Kit --> <dependency> <groupId>io.prismic</groupId> <artifactId>java-kit</artifactId> <version>2.6.0</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Caching --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.6</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Validation --> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>8.0.0.Final</version> </dependency> </dependencies>
Configuration Management
PrismicConfig.java:
@Configuration @ConfigurationProperties(prefix = "prismic") @Data public class PrismicConfig { private String repositoryName; private String accessToken; private String apiUrl = "https://{repository}.prismic.io/api/v2"; private CacheConfig cache = new CacheConfig(); private PreviewConfig preview = new PreviewConfig(); private WebhookConfig webhook = new WebhookConfig(); @Data public static class CacheConfig { private boolean enabled = true; private Duration ttl = Duration.ofMinutes(5); private int maxSize = 1000; private CacheType type = CacheType.CAFFEINE; } @Data public static class PreviewConfig { private boolean enabled = true; private String previewToken; private Duration sessionDuration = Duration.ofHours(2); private String redirectUrl = "/preview"; } @Data public static class WebhookConfig { private String secret; private String endpoint = "/api/webhooks/prismic"; private List<String> allowedIps = new ArrayList<>(); } public String getApiUrl() { return apiUrl.replace("{repository}", repositoryName); } public enum CacheType { CAFFEINE, REDIS, MEMORY } } Prismic Client Service
PrismicClientService.java:
@Service @Slf4j public class PrismicClientService { private final PrismicConfig config; private final Api api; private final Cache<String, Object> cache; private final ObjectMapper objectMapper; public PrismicClientService(PrismicConfig config) { this.config = config; this.api = initializeApi(); this.cache = createCache(); this.objectMapper = new ObjectMapper(); } private Api initializeApi() { try { Cache cache = new Cache.BuiltInCache(1000); Logger logger = new Logger.PrintlnLogger(); HttpClient httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); return Api.get(config.getApiUrl(), config.getAccessToken(), httpClient, cache, logger); } catch (Exception e) { throw new PrismicException("Failed to initialize Prismic API", e); } } private Cache<String, Object> createCache() { if (!config.getCache().isEnabled()) { return null; } return Caffeine.newBuilder() .expireAfterWrite(config.getCache().getTtl()) .maximumSize(config.getCache().getMaxSize()) .build(); } public Document getDocumentById(String documentId, String language) { String cacheKey = String.format("doc:%s:%s", documentId, language); if (cache != null) { Document cached = (Document) cache.getIfPresent(cacheKey); if (cached != null) { return cached; } } try { Document document = api.getByID(documentId, api.getData().getRef(), language); if (cache != null && document != null) { cache.put(cacheKey, document); } return document; } catch (Exception e) { throw new PrismicException("Failed to get document by ID: " + documentId, e); } } public List<Document> getDocumentsByType(String documentType, String language) { String cacheKey = String.format("docs:type:%s:%s", documentType, language); if (cache != null) { @SuppressWarnings("unchecked") List<Document> cached = (List<Document>) cache.getIfPresent(cacheKey); if (cached != null) { return cached; } } try { Response response = api.query( Predicates.at("document.type", documentType), new QueryOptions() .lang(language) .ref(api.getData().getRef()) ); List<Document> documents = response.getResults(); if (cache != null) { cache.put(cacheKey, documents); } return documents; } catch (Exception e) { throw new PrismicException("Failed to get documents by type: " + documentType, e); } } public Document getSingleDocument(String documentType, String language) { List<Document> documents = getDocumentsByType(documentType, language); return documents.isEmpty() ? null : documents.get(0); } public Response searchDocuments(String query, String language, Map<String, String> filters, PaginationParams pagination) { try { List<Predicate> predicates = new ArrayList<>(); // Add search query if (query != null && !query.trim().isEmpty()) { predicates.add(Predicates.fulltext("document", query)); } // Add filters if (filters != null) { filters.forEach((field, value) -> predicates.add(Predicates.at(field, value))); } // Build query options QueryOptions options = new QueryOptions() .lang(language) .ref(api.getData().getRef()); if (pagination != null) { options.page(pagination.getPage()) .pageSize(pagination.getPageSize()); } // Execute query return api.query(predicates.toArray(new Predicate[0]), options); } catch (Exception e) { throw new PrismicException("Document search failed", e); } } public List<Document> getDocumentsByTags(List<String> tags, String language) { String cacheKey = String.format("docs:tags:%s:%s", String.join(",", tags), language); if (cache != null) { @SuppressWarnings("unchecked") List<Document> cached = (List<Document>) cache.getIfPresent(cacheKey); if (cached != null) { return cached; } } try { Response response = api.query( Predicates.any("document.tags", tags), new QueryOptions() .lang(language) .ref(api.getData().getRef()) ); List<Document> documents = response.getResults(); if (cache != null) { cache.put(cacheKey, documents); } return documents; } catch (Exception e) { throw new PrismicException("Failed to get documents by tags", e); } } public Map<String, Object> getDocumentAsMap(Document document) { try { Map<String, Object> result = new HashMap<>(); result.put("id", document.getId()); result.put("type", document.getType()); result.put("tags", document.getTags()); result.put("slug", document.getSlug()); result.put("lang", document.getLang()); result.put("lastPublicationDate", document.getLastPublicationDate()); // Convert fragments to map Map<String, Object> data = new HashMap<>(); for (String fragmentName : document.getFragments().keySet()) { Fragment fragment = document.getFragment(fragmentName); data.put(fragmentName, convertFragmentToObject(fragment)); } result.put("data", data); return result; } catch (Exception e) { throw new PrismicException("Failed to convert document to map", e); } } private Object convertFragmentToObject(Fragment fragment) { if (fragment instanceof Group) { return convertGroupToMap((Group) fragment); } else if (fragment instanceof StructuredText) { return convertStructuredTextToObject((StructuredText) fragment); } else if (fragment instanceof Image) { return convertImageToMap((Image) fragment); } else if (fragment instanceof Link) { return convertLinkToMap((Link) fragment); } else if (fragment instanceof Date) { return ((Date) fragment).getValue(); } else if (fragment instanceof Number) { return ((Number) fragment).getValue(); } else if (fragment instanceof Text) { return ((Text) fragment).getValue(); } else if (fragment instanceof Color) { return ((Color) fragment).getHexValue(); } else if (fragment instanceof Select) { return ((Select) fragment).getValue(); } else if (fragment instanceof Embed) { return convertEmbedToMap((Embed) fragment); } else { return fragment.toString(); } } private Map<String, Object> convertGroupToMap(Group group) { List<Map<String, Object>> groupData = new ArrayList<>(); for (GroupDoc groupDoc : group.getGroupDocs()) { Map<String, Object> docData = new HashMap<>(); for (String fragmentName : groupDoc.getFragments().keySet()) { Fragment fragment = groupDoc.getFragment(fragmentName); docData.put(fragmentName, convertFragmentToObject(fragment)); } groupData.add(docData); } return Map.of("group", groupData); } private Object convertStructuredTextToObject(StructuredText structuredText) { if (structuredText.getParagraphs().isEmpty()) { return structuredText.getFirstParagraph() != null ? structuredText.getFirstParagraph().getText() : ""; } List<Map<String, Object>> blocks = new ArrayList<>(); for (StructuredText.Block block : structuredText.getBlocks()) { Map<String, Object> blockData = new HashMap<>(); blockData.put("type", block.getLabel()); blockData.put("text", block.getText()); if (block instanceof StructuredText.Heading) { blockData.put("level", ((StructuredText.Heading) block).getLevel()); } if (block instanceof StructuredText.ListItem) { blockData.put("ordered", ((StructuredText.ListItem) block).isOrdered()); } blocks.add(blockData); } return Map.of("blocks", blocks, "text", structuredText.getFirstParagraph().getText()); } private Map<String, Object> convertImageToMap(Image image) { Map<String, Object> imageData = new HashMap<>(); imageData.put("url", image.getUrl()); imageData.put("alt", image.getAlt()); imageData.put("copyright", image.getCopyright()); Map<String, Object> dimensions = new HashMap<>(); dimensions.put("width", image.getWidth()); dimensions.put("height", image.getHeight()); imageData.put("dimensions", dimensions); // Add views (different image sizes) Map<String, Object> views = new HashMap<>(); for (Image.View view : image.getViews()) { Map<String, Object> viewData = new HashMap<>(); viewData.put("url", view.getUrl()); viewData.put("width", view.getWidth()); viewData.put("height", view.getHeight()); views.put(view.getName(), viewData); } imageData.put("views", views); return imageData; } private Map<String, Object> convertLinkToMap(Link link) { Map<String, Object> linkData = new HashMap<>(); linkData.put("url", link.getUrl()); linkData.put("target", link.getTarget()); if (link instanceof DocumentLink) { DocumentLink docLink = (DocumentLink) link; linkData.put("type", "document"); linkData.put("documentId", docLink.getId()); linkData.put("isBroken", docLink.isBroken()); } else if (link instanceof WebLink) { linkData.put("type", "web"); } else if (link instanceof FileLink) { FileLink fileLink = (FileLink) link; linkData.put("type", "file"); linkData.put("name", fileLink.getFilename()); linkData.put("size", fileLink.getFilesize()); } else if (link instanceof ImageLink) { linkData.put("type", "image"); } return linkData; } private Map<String, Object> convertEmbedToMap(Embed embed) { Map<String, Object> embedData = new HashMap<>(); embedData.put("html", embed.getHtml()); embedData.put("type", embed.getType()); embedData.put("provider", embed.getProvider()); embedData.put("url", embed.getUrl()); Map<String, Object> dimensions = new HashMap<>(); dimensions.put("width", embed.getWidth()); dimensions.put("height", embed.getHeight()); embedData.put("dimensions", dimensions); return embedData; } public void clearCache() { if (cache != null) { cache.invalidateAll(); } } public void clearCacheForType(String documentType) { if (cache != null) { // Invalidate all cache entries for this document type cache.asMap().keySet().stream() .filter(key -> key.startsWith("docs:type:" + documentType)) .forEach(cache::invalidate); } } public Api getApi() { return api; } } Content Management Service
ContentService.java:
@Service @Slf4j public class ContentService { private final PrismicClientService prismicClient; private final ObjectMapper objectMapper; public ContentService(PrismicClientService prismicClient) { this.prismicClient = prismicClient; this.objectMapper = new ObjectMapper(); } public ContentResponse getPageContent(String pageId, String language) { try { Document document = prismicClient.getDocumentById(pageId, language); if (document == null) { throw new ContentNotFoundException("Page not found: " + pageId); } Map<String, Object> content = prismicClient.getDocumentAsMap(document); return ContentResponse.builder() .success(true) .content(content) .metadata(extractMetadata(document)) .build(); } catch (Exception e) { log.error("Failed to get page content: {}", pageId, e); return ContentResponse.builder() .success(false) .error(e.getMessage()) .build(); } } public ContentResponse getContentByType(String contentType, String language) { try { List<Document> documents = prismicClient.getDocumentsByType(contentType, language); List<Map<String, Object>> contentList = documents.stream() .map(prismicClient::getDocumentAsMap) .collect(Collectors.toList()); return ContentResponse.builder() .success(true) .content(contentList) .metadata(Map.of("count", contentList.size())) .build(); } catch (Exception e) { log.error("Failed to get content by type: {}", contentType, e); return ContentResponse.builder() .success(false) .error(e.getMessage()) .build(); } } public ContentResponse searchContent(ContentSearchRequest request) { try { Response response = prismicClient.searchDocuments( request.getQuery(), request.getLanguage(), request.getFilters(), request.getPagination() ); List<Map<String, Object>> results = response.getResults().stream() .map(prismicClient::getDocumentAsMap) .collect(Collectors.toList()); PaginationMetadata pagination = PaginationMetadata.builder() .page(response.getPage()) .resultsPerPage(response.getResultsPerPage()) .totalResults(response.getTotalResultsSize()) .totalPages(response.getTotalPages()) .build(); return ContentResponse.builder() .success(true) .content(results) .pagination(pagination) .metadata(Map.of("query", request.getQuery())) .build(); } catch (Exception e) { log.error("Content search failed", e); return ContentResponse.builder() .success(false) .error(e.getMessage()) .build(); } } public ContentResponse getContentBySlug(String contentType, String slug, String language) { try { List<Document> documents = prismicClient.getDocumentsByType(contentType, language); Optional<Document> document = documents.stream() .filter(doc -> slug.equals(doc.getSlug())) .findFirst(); if (document.isPresent()) { Map<String, Object> content = prismicClient.getDocumentAsMap(document.get()); return ContentResponse.builder() .success(true) .content(content) .metadata(extractMetadata(document.get())) .build(); } else { return ContentResponse.builder() .success(false) .error("Content not found for slug: " + slug) .build(); } } catch (Exception e) { log.error("Failed to get content by slug: {}", slug, e); return ContentResponse.builder() .success(false) .error(e.getMessage()) .build(); } } public NavigationResponse getNavigation(String navigationId, String language) { try { Document navigationDoc = prismicClient.getDocumentById(navigationId, language); if (navigationDoc == null) { throw new ContentNotFoundException("Navigation not found: " + navigationId); } Map<String, Object> navigationData = prismicClient.getDocumentAsMap(navigationDoc); List<NavigationItem> navItems = extractNavigationItems(navigationData); return NavigationResponse.builder() .success(true) .navigation(navItems) .metadata(extractMetadata(navigationDoc)) .build(); } catch (Exception e) { log.error("Failed to get navigation: {}", navigationId, e); return NavigationResponse.builder() .success(false) .error(e.getMessage()) .build(); } } public SettingsResponse getSiteSettings(String settingsId, String language) { try { Document settingsDoc = prismicClient.getDocumentById(settingsId, language); if (settingsDoc == null) { throw new ContentNotFoundException("Settings not found: " + settingsId); } Map<String, Object> settingsData = prismicClient.getDocumentAsMap(settingsDoc); SiteSettings settings = extractSiteSettings(settingsData); return SettingsResponse.builder() .success(true) .settings(settings) .metadata(extractMetadata(settingsDoc)) .build(); } catch (Exception e) { log.error("Failed to get site settings: {}", settingsId, e); return SettingsResponse.builder() .success(false) .error(e.getMessage()) .build(); } } public ContentPreviewResponse previewContent(String token, String language) { try { // Use preview token to get preview content Api api = prismicClient.getApi(); String previewRef = api.getPreviewSession(token, config.getPreview().getRedirectUrl(), api.getData().getRefs()); // Query documents with preview ref Response response = api.query( Predicates.at("document.type", "page"), new QueryOptions() .lang(language) .ref(previewRef) ); List<Map<String, Object>> previewContent = response.getResults().stream() .map(prismicClient::getDocumentAsMap) .collect(Collectors.toList()); return ContentPreviewResponse.builder() .success(true) .content(previewContent) .previewRef(previewRef) .build(); } catch (Exception e) { log.error("Content preview failed", e); return ContentPreviewResponse.builder() .success(false) .error(e.getMessage()) .build(); } } private Map<String, Object> extractMetadata(Document document) { Map<String, Object> metadata = new HashMap<>(); metadata.put("id", document.getId()); metadata.put("type", document.getType()); metadata.put("lastModified", document.getLastPublicationDate()); metadata.put("tags", document.getTags()); metadata.put("language", document.getLang()); return metadata; } private List<NavigationItem> extractNavigationItems(Map<String, Object> navigationData) { List<NavigationItem> navItems = new ArrayList<>(); try { @SuppressWarnings("unchecked") Map<String, Object> data = (Map<String, Object>) navigationData.get("data"); if (data != null && data.containsKey("navigation_links")) { @SuppressWarnings("unchecked") Map<String, Object> navLinks = (Map<String, Object>) data.get("navigation_links"); if (navLinks.containsKey("group")) { @SuppressWarnings("unchecked") List<Map<String, Object>> links = (List<Map<String, Object>>) navLinks.get("group"); for (Map<String, Object> link : links) { NavigationItem item = NavigationItem.builder() .label((String) link.get("label")) .url((String) link.get("url")) .target((String) link.get("target")) .build(); navItems.add(item); } } } } catch (Exception e) { log.warn("Failed to extract navigation items", e); } return navItems; } private SiteSettings extractSiteSettings(Map<String, Object> settingsData) { SiteSettings.SiteSettingsBuilder builder = SiteSettings.builder(); try { @SuppressWarnings("unchecked") Map<String, Object> data = (Map<String, Object>) settingsData.get("data"); if (data != null) { builder.siteTitle(getStringValue(data, "site_title")) .siteDescription(getStringValue(data, "site_description")) .logo(extractImage(data, "logo")) .favicon(extractImage(data, "favicon")) .socialLinks(extractSocialLinks(data)); } } catch (Exception e) { log.warn("Failed to extract site settings", e); } return builder.build(); } private String getStringValue(Map<String, Object> data, String key) { try { @SuppressWarnings("unchecked") Map<String, Object> field = (Map<String, Object>) data.get(key); return field != null ? (String) field.get("text") : null; } catch (Exception e) { return null; } } private ImageData extractImage(Map<String, Object> data, String key) { try { @SuppressWarnings("unchecked") Map<String, Object> imageField = (Map<String, Object>) data.get(key); if (imageField != null) { return ImageData.builder() .url((String) imageField.get("url")) .alt((String) imageField.get("alt")) .build(); } } catch (Exception e) { log.debug("Failed to extract image: {}", key, e); } return null; } private Map<String, String> extractSocialLinks(Map<String, Object> data) { Map<String, String> socialLinks = new HashMap<>(); try { @SuppressWarnings("unchecked") Map<String, Object> socialField = (Map<String, Object>) data.get("social_links"); if (socialField != null && socialField.containsKey("group")) { @SuppressWarnings("unchecked") List<Map<String, Object>> links = (List<Map<String, Object>>) socialField.get("group"); for (Map<String, Object> link : links) { String platform = (String) link.get("platform"); String url = (String) link.get("url"); if (platform != null && url != null) { socialLinks.put(platform, url); } } } } catch (Exception e) { log.debug("Failed to extract social links", e); } return socialLinks; } } Webhook Handler
PrismicWebhookHandler.java:
@Service @Slf4j public class PrismicWebhookHandler { private final PrismicClientService prismicClient; private final PrismicConfig config; private final ObjectMapper objectMapper; public PrismicWebhookHandler(PrismicClientService prismicClient, PrismicConfig config) { this.prismicClient = prismicClient; this.config = config; this.objectMapper = new ObjectMapper(); } public WebhookResponse handleWebhook(WebhookRequest request, HttpServletRequest httpRequest) { try { // Verify webhook secret if (!isValidWebhook(request, httpRequest)) { log.warn("Invalid webhook request received"); return WebhookResponse.builder() .success(false) .message("Invalid webhook signature") .build(); } String eventType = request.getType(); log.info("Processing webhook event: {}", eventType); switch (eventType) { case "api-update": handleApiUpdate(request); break; case "document-update": handleDocumentUpdate(request); break; case "document-create": handleDocumentCreate(request); break; case "document-delete": handleDocumentDelete(request); break; case "experiment-update": handleExperimentUpdate(request); break; default: log.info("Unhandled webhook event type: {}", eventType); } return WebhookResponse.builder() .success(true) .message("Webhook processed successfully") .build(); } catch (Exception e) { log.error("Webhook processing failed", e); return WebhookResponse.builder() .success(false) .message("Webhook processing failed: " + e.getMessage()) .build(); } } private void handleApiUpdate(WebhookRequest request) { log.info("API update detected, clearing cache"); prismicClient.clearCache(); // Perform any API-level cache invalidation broadcastCacheClear(); } private void handleDocumentUpdate(WebhookRequest request) { String documentId = request.getDocumentId(); String documentType = extractDocumentType(request); log.info("Document update: {} (type: {})", documentId, documentType); // Clear cache for this document and its type prismicClient.clearCacheForType(documentType); // Clear specific document cache String cacheKey = String.format("doc:%s:%s", documentId, "*"); clearCacheByPattern(cacheKey); // Notify subscribers notifyDocumentUpdate(documentId, documentType); } private void handleDocumentCreate(WebhookRequest request) { String documentId = request.getDocumentId(); String documentType = extractDocumentType(request); log.info("Document created: {} (type: {})", documentId, documentType); // Clear cache for this document type prismicClient.clearCacheForType(documentType); // Notify subscribers notifyDocumentCreate(documentId, documentType); } private void handleDocumentDelete(WebhookRequest request) { String documentId = request.getDocumentId(); String documentType = extractDocumentType(request); log.info("Document deleted: {} (type: {})", documentId, documentType); // Clear cache for this document type prismicClient.clearCacheForType(documentType); // Clear specific document cache String cacheKey = String.format("doc:%s:%s", documentId, "*"); clearCacheByPattern(cacheKey); // Notify subscribers notifyDocumentDelete(documentId, documentType); } private void handleExperimentUpdate(WebhookRequest request) { log.info("Experiment update detected"); // Clear all cache as experiments might affect multiple documents prismicClient.clearCache(); // Notify about experiment changes notifyExperimentUpdate(); } private boolean isValidWebhook(WebhookRequest request, HttpServletRequest httpRequest) { // Verify IP address if (!isAllowedIp(httpRequest.getRemoteAddr())) { log.warn("Webhook from unauthorized IP: {}", httpRequest.getRemoteAddr()); return false; } // Verify secret (if configured) if (config.getWebhook().getSecret() != null) { String signature = httpRequest.getHeader("X-Prismic-Secret"); if (!config.getWebhook().getSecret().equals(signature)) { log.warn("Invalid webhook signature"); return false; } } return true; } private boolean isAllowedIp(String ipAddress) { if (config.getWebhook().getAllowedIps().isEmpty()) { return true; // No restrictions } return config.getWebhook().getAllowedIps().contains(ipAddress); } private String extractDocumentType(WebhookRequest request) { try { // Extract document type from webhook payload if (request.getDocuments() != null && !request.getDocuments().isEmpty()) { return request.getDocuments().get(0); // Simplified } } catch (Exception e) { log.warn("Failed to extract document type from webhook", e); } return "unknown"; } private void clearCacheByPattern(String pattern) { // Implementation would depend on cache implementation // This is a simplified version log.debug("Clearing cache for pattern: {}", pattern); } private void broadcastCacheClear() { // Notify other instances in a distributed environment // This could use Redis pub/sub, Kafka, or other messaging systems log.info("Broadcasting cache clear to all instances"); } private void notifyDocumentUpdate(String documentId, String documentType) { // Notify subscribers about document updates // This could be through WebSocket, Server-Sent Events, or message queues log.debug("Notifying subscribers about document update: {}", documentId); } private void notifyDocumentCreate(String documentId, String documentType) { log.debug("Notifying subscribers about document creation: {}", documentId); } private void notifyDocumentDelete(String documentId, String documentType) { log.debug("Notifying subscribers about document deletion: {}", documentId); } private void notifyExperimentUpdate() { log.debug("Notifying subscribers about experiment update"); } } Response Models
ContentResponse.java:
@Data @Builder public class ContentResponse { private boolean success; private Object content; private Map<String, Object> metadata; private PaginationMetadata pagination; private String error; @Data @Builder public static class PaginationMetadata { private int page; private int resultsPerPage; private int totalResults; private int totalPages; private String nextPage; private String prevPage; } } @Data @Builder public class NavigationResponse { private boolean success; private List<NavigationItem> navigation; private Map<String, Object> metadata; private String error; } @Data @Builder public class SettingsResponse { private boolean success; private SiteSettings settings; private Map<String, Object> metadata; private String error; } @Data @Builder public class ContentPreviewResponse { private boolean success; private Object content; private String previewRef; private String error; } @Data @Builder public class WebhookResponse { private boolean success; private String message; } @Data @Builder public class NavigationItem { private String label; private String url; private String target; private List<NavigationItem> children; } @Data @Builder public class SiteSettings { private String siteTitle; private String siteDescription; private ImageData logo; private ImageData favicon; private Map<String, String> socialLinks; @Data @Builder public static class ImageData { private String url; private String alt; private Integer width; private Integer height; } } @Data @Builder public class ContentSearchRequest { private String query; private String language; private Map<String, String> filters; private PaginationParams pagination; @Data @Builder public static class PaginationParams { private int page = 1; private int pageSize = 20; } } @Data public class WebhookRequest { private String type; private String domain; private String apiUrl; private String secret; private List<String> documents; private List<String> releases; private List<String> tags; private String documentId; @JsonAnySetter private Map<String, Object> additionalProperties = new HashMap<>(); } REST API Controller
PrismicController.java:
@RestController @RequestMapping("/api/content") @Slf4j public class PrismicController { private final ContentService contentService; private final PrismicWebhookHandler webhookHandler; public PrismicController(ContentService contentService, PrismicWebhookHandler webhookHandler) { this.contentService = contentService; this.webhookHandler = webhookHandler; } @GetMapping("/pages/{pageId}") public ResponseEntity<ContentResponse> getPage( @PathVariable String pageId, @RequestParam(defaultValue = "en-us") String language) { ContentResponse response = contentService.getPageContent(pageId, language); return createResponse(response); } @GetMapping("/types/{contentType}") public ResponseEntity<ContentResponse> getContentByType( @PathVariable String contentType, @RequestParam(defaultValue = "en-us") String language) { ContentResponse response = contentService.getContentByType(contentType, language); return createResponse(response); } @GetMapping("/types/{contentType}/slug/{slug}") public ResponseEntity<ContentResponse> getContentBySlug( @PathVariable String contentType, @PathVariable String slug, @RequestParam(defaultValue = "en-us") String language) { ContentResponse response = contentService.getContentBySlug(contentType, slug, language); return createResponse(response); } @PostMapping("/search") public ResponseEntity<ContentResponse> searchContent( @RequestBody ContentSearchRequest request) { ContentResponse response = contentService.searchContent(request); return createResponse(response); } @GetMapping("/navigation/{navigationId}") public ResponseEntity<NavigationResponse> getNavigation( @PathVariable String navigationId, @RequestParam(defaultValue = "en-us") String language) { NavigationResponse response = contentService.getNavigation(navigationId, language); return createResponse(response); } @GetMapping("/settings/{settingsId}") public ResponseEntity<SettingsResponse> getSiteSettings( @PathVariable String settingsId, @RequestParam(defaultValue = "en-us") String language) { SettingsResponse response = contentService.getSiteSettings(settingsId, language); return createResponse(response); } @PostMapping("/preview") public ResponseEntity<ContentPreviewResponse> previewContent( @RequestParam String token, @RequestParam(defaultValue = "en-us") String language) { ContentPreviewResponse response = contentService.previewContent(token, language); return createResponse(response); } @PostMapping("/webhooks/prismic") public ResponseEntity<WebhookResponse> handleWebhook( @RequestBody WebhookRequest request, HttpServletRequest httpRequest) { WebhookResponse response = webhookHandler.handleWebhook(request, httpRequest); return createResponse(response); } @PostMapping("/cache/clear") public ResponseEntity<Map<String, Object>> clearCache( @RequestParam(required = false) String documentType) { try { if (documentType != null) { // Clear cache for specific document type contentService.clearCacheForType(documentType); } else { // Clear all cache contentService.clearCache(); } return ResponseEntity.ok(Map.of( "success", true, "message", "Cache cleared successfully" )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of( "success", false, "error", e.getMessage() )); } } private <T> ResponseEntity<T> createResponse(T response) { try { // Check if response indicates success Method successMethod = response.getClass().getMethod("isSuccess"); Boolean isSuccess = (Boolean) successMethod.invoke(response); if (Boolean.TRUE.equals(isSuccess)) { return ResponseEntity.ok(response); } else { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(response); } } catch (Exception e) { // If we can't determine success, return as is return ResponseEntity.ok(response); } } } Best Practices
- Implement caching to reduce API calls to Prismic.io
- Use webhooks for real-time content updates
- Handle errors gracefully with proper error responses
- Implement content preview for editors
- Use structured content types for consistent data
- Implement proper logging for debugging and monitoring
- Secure webhook endpoints with IP whitelisting and secrets
Conclusion
Prismic.io integration in Java provides:
- Efficient content retrieval with caching
- Structured content management with custom types
- Multi-language support for global applications
- Real-time updates through webhooks
- Content preview capabilities
- RESTful API for easy integration
This implementation demonstrates how to build a robust content management system using Prismic.io as a headless CMS, providing flexibility and performance for modern web applications.