google-site-verification: google61fe8ba583a51912.html
Prismic.io Integration in Java: Headless CMS Content Management

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

  1. Implement caching to reduce API calls to Prismic.io
  2. Use webhooks for real-time content updates
  3. Handle errors gracefully with proper error responses
  4. Implement content preview for editors
  5. Use structured content types for consistent data
  6. Implement proper logging for debugging and monitoring
  7. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper