This guide provides comprehensive Java implementations for interacting with WordPress REST API, including authentication, CRUD operations, media management, and custom extensions.
WordPress REST API Overview
Key Features:
- RESTful API for WordPress content management
- Support for posts, pages, users, media, comments
- OAuth1, JWT, and API key authentication
- Extensible with custom endpoints
Common Use Cases:
- Headless WordPress implementations
- Mobile app backends
- Content syndication
- Automated content publishing
Dependencies and Setup
Maven Dependencies
<properties> <spring-boot.version>3.1.0</spring-boot.version> <jackson.version>2.15.2</jackson.version> <okhttp.version>4.11.0</okhttp.version> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>${okhttp.version}</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>${jackson.version}</version> </dependency> <!-- Utilities --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring-boot.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>mockwebserver</artifactId> <version>${okhttp.version}</version> <scope>test</scope> </dependency> </dependencies> Application Configuration
# application.yml app: wordpress: # WordPress site configuration base-url: "https://your-wordpress-site.com" api-prefix: "/wp-json/wp/v2" timeout: 30000 # Authentication auth: type: "JWT" # JWT, BASIC, OAUTH1, API_KEY jwt: username: "${WP_USERNAME:admin}" password: "${WP_PASSWORD:password}" token-endpoint: "/wp-json/jwt-auth/v1/token" basic: username: "${WP_USERNAME}" password: "${WP_PASSWORD}" api-key: key: "${WP_API_KEY}" header: "X-API-Key" # Cache configuration cache: enabled: true ttl: 300 # 5 minutes # Retry configuration retry: max-attempts: 3 backoff-delay: 1000 server: port: 8080 logging: level: com.example.wordpressapi: DEBUG Core Models
1. WordPress API Models
// WordPressPost.java package com.example.wordpressapi.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @Data @Builder public class WordPressPost { private Long id; private LocalDateTime date; private LocalDateTime dateGmt; private LocalDateTime modified; private LocalDateTime modifiedGmt; private String slug; private String status; private String type; private String link; private String title; private String content; private String excerpt; private Long author; private Long featuredMedia; private String commentStatus; private String pingStatus; private Boolean sticky; private String template; private String format; private List<Long> categories; private List<Long> tags; private Map<String, Object> meta; private List<Object> revisions; // Rendering properties private RenderedContent titleRendered; private RenderedContent contentRendered; private RenderedContent excerptRendered; @Data @Builder public static class RenderedContent { private String raw; private String rendered; private Boolean protected; } } // WordPressUser.java package com.example.wordpressapi.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.Map; @Data @Builder public class WordPressUser { private Long id; private String username; private String name; private String firstName; private String lastName; private String email; private String url; private String description; private String link; private String locale; private String nickname; private String slug; private LocalDateTime registeredDate; private Map<String, String> capabilities; private String extraCapabilities; private Map<String, String> avatarUrls; private List<String> roles; private Map<String, Object> meta; } // WordPressMedia.java package com.example.wordpressapi.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.Map; @Data @Builder public class WordPressMedia { private Long id; private LocalDateTime date; private LocalDateTime dateGmt; private LocalDateTime modified; private LocalDateTime modifiedGmt; private String slug; private String status; private String type; private String link; private String title; private String author; private String commentStatus; private String pingStatus; private String template; private Map<String, Object> meta; private String description; private String caption; private String altText; private String mediaType; private String mimeType; private MediaDetails mediaDetails; private String post; private String sourceUrl; private Map<String, String> links; @Data @Builder public static class MediaDetails { private Integer width; private Integer height; private String file; private Map<String, ImageSize> sizes; private ImageMeta imageMeta; @Data @Builder public static class ImageSize { private String file; private Integer width; private Integer height; private String mimeType; private String sourceUrl; } @Data @Builder public static class ImageMeta { private String aperture; private String credit; private String camera; private String caption; private String createdTimestamp; private String copyright; private String focalLength; private String iso; private String shutterSpeed; private String title; private String orientation; private List<String> keywords; } } } // WordPressCategory.java package com.example.wordpressapi.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.Map; @Data @Builder public class WordPressCategory { private Long id; private Integer count; private String description; private String link; private String name; private String slug; private String taxonomy; private Long parent; private Map<String, Object> meta; } // WordPressTag.java package com.example.wordpressapi.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.Map; @Data @Builder public class WordPressTag { private Long id; private Integer count; private String description; private String link; private String name; private String slug; private String taxonomy; private Map<String, Object> meta; } 2. Request and Response Models
// ApiResponse.java package com.example.wordpressapi.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @Data @Builder public class ApiResponse<T> { private boolean success; private String message; private LocalDateTime timestamp; private T data; private PaginationInfo pagination; private Map<String, Object> metadata; @Data @Builder public static class PaginationInfo { private Integer total; private Integer totalPages; private Integer currentPage; private Integer perPage; private Boolean hasNext; private Boolean hasPrevious; private String nextPageUrl; private String previousPageUrl; } public static <T> ApiResponse<T> success(T data) { return ApiResponse.<T>builder() .success(true) .message("Operation completed successfully") .timestamp(LocalDateTime.now()) .data(data) .build(); } public static <T> ApiResponse<T> error(String message) { return ApiResponse.<T>builder() .success(false) .message(message) .timestamp(LocalDateTime.now()) .build(); } } // CreatePostRequest.java package com.example.wordpressapi.model; import lombok.Data; import jakarta.validation.constraints.NotBlank; import java.util.List; import java.util.Map; @Data public class CreatePostRequest { @NotBlank(message = "Title is required") private String title; @NotBlank(message = "Content is required") private String content; private String excerpt; private String status = "draft"; // draft, publish, pending, private private String slug; private List<Long> categories; private List<Long> tags; private Boolean sticky = false; private String format = "standard"; // standard, aside, image, video, etc. private Map<String, Object> meta; private Long featuredMedia; } // UpdatePostRequest.java package com.example.wordpressapi.model; import lombok.Data; import java.util.List; import java.util.Map; @Data public class UpdatePostRequest { private String title; private String content; private String excerpt; private String status; private String slug; private List<Long> categories; private List<Long> tags; private Boolean sticky; private String format; private Map<String, Object> meta; private Long featuredMedia; } // SearchCriteria.java package com.example.wordpressapi.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.List; @Data @Builder public class SearchCriteria { private Integer page; private Integer perPage; private String search; private String orderBy; private String order; private String status; private List<Long> categories; private List<Long> tags; private Long author; private LocalDateTime after; private LocalDateTime before; private String slug; private String type; public static SearchCriteria defaultCriteria() { return SearchCriteria.builder() .page(1) .perPage(10) .orderBy("date") .order("desc") .build(); } } 3. Authentication Models
// AuthConfig.java package com.example.wordpressapi.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "app.wordpress.auth") public class AuthConfig { private AuthType type = AuthType.JWT; private JwtConfig jwt = new JwtConfig(); private BasicAuthConfig basic = new BasicAuthConfig(); private OAuth1Config oauth1 = new OAuth1Config(); private ApiKeyConfig apiKey = new ApiKeyConfig(); public enum AuthType { JWT, BASIC, OAUTH1, API_KEY, NONE } @Data public static class JwtConfig { private String username; private String password; private String tokenEndpoint = "/wp-json/jwt-auth/v1/token"; private String validateEndpoint = "/wp-json/jwt-auth/v1/token/validate"; } @Data public static class BasicAuthConfig { private String username; private String password; } @Data public static class OAuth1Config { private String consumerKey; private String consumerSecret; private String accessToken; private String accessSecret; } @Data public static class ApiKeyConfig { private String key; private String header = "X-API-Key"; } } // JwtAuthResponse.java package com.example.wordpressapi.model; import lombok.Data; @Data public class JwtAuthResponse { private String token; private String userDisplayName; private String userEmail; private String userNicename; } WordPress Client Implementation
1. HTTP Client Service
// WordPressHttpClient.java package com.example.wordpressapi.service; import com.example.wordpressapi.config.AuthConfig; import com.example.wordpressapi.model.JwtAuthResponse; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @Slf4j @Service public class WordPressHttpClient { private final OkHttpClient httpClient; private final ObjectMapper objectMapper; private final AuthConfig authConfig; private final String baseUrl; private final String apiPrefix; private String jwtToken; private long tokenExpiry; private final Map<String, Response> responseCache = new ConcurrentHashMap<>(); public WordPressHttpClient(@Value("${app.wordpress.base-url}") String baseUrl, @Value("${app.wordpress.api-prefix:/wp-json/wp/v2}") String apiPrefix, @Value("${app.wordpress.timeout:30000}") int timeout, AuthConfig authConfig, ObjectMapper objectMapper) { this.baseUrl = baseUrl; this.apiPrefix = apiPrefix; this.authConfig = authConfig; this.objectMapper = objectMapper; this.httpClient = new OkHttpClient.Builder() .connectTimeout(timeout, TimeUnit.MILLISECONDS) .readTimeout(timeout, TimeUnit.MILLISECONDS) .writeTimeout(timeout, TimeUnit.MILLISECONDS) .addInterceptor(new RetryInterceptor()) .addInterceptor(new AuthInterceptor()) .addInterceptor(new LoggingInterceptor()) .build(); } /** * Make GET request to WordPress API */ public <T> T get(String endpoint, Class<T> responseType, Map<String, String> queryParams) { try { HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl + apiPrefix + endpoint).newBuilder(); if (queryParams != null) { queryParams.forEach(urlBuilder::addQueryParameter); } Request request = new Request.Builder() .url(urlBuilder.build()) .get() .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new WordPressApiException("HTTP " + response.code() + ": " + response.message()); } String responseBody = response.body().string(); return objectMapper.readValue(responseBody, responseType); } } catch (IOException e) { log.error("GET request failed for endpoint: {}", endpoint, e); throw new WordPressApiException("Failed to execute GET request", e); } } /** * Make POST request to WordPress API */ public <T> T post(String endpoint, Object body, Class<T> responseType) { try { String jsonBody = objectMapper.writeValueAsString(body); Request request = new Request.Builder() .url(baseUrl + apiPrefix + endpoint) .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new WordPressApiException("HTTP " + response.code() + ": " + response.message()); } String responseBody = response.body().string(); return objectMapper.readValue(responseBody, responseType); } } catch (IOException e) { log.error("POST request failed for endpoint: {}", endpoint, e); throw new WordPressApiException("Failed to execute POST request", e); } } /** * Make PUT request to WordPress API */ public <T> T put(String endpoint, Object body, Class<T> responseType) { try { String jsonBody = objectMapper.writeValueAsString(body); Request request = new Request.Builder() .url(baseUrl + apiPrefix + endpoint) .put(RequestBody.create(jsonBody, MediaType.parse("application/json"))) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new WordPressApiException("HTTP " + response.code() + ": " + response.message()); } String responseBody = response.body().string(); return objectMapper.readValue(responseBody, responseType); } } catch (IOException e) { log.error("PUT request failed for endpoint: {}", endpoint, e); throw new WordPressApiException("Failed to execute PUT request", e); } } /** * Make DELETE request to WordPress API */ public boolean delete(String endpoint) { try { Request request = new Request.Builder() .url(baseUrl + apiPrefix + endpoint) .delete() .build(); try (Response response = httpClient.newCall(request).execute()) { return response.isSuccessful(); } } catch (IOException e) { log.error("DELETE request failed for endpoint: {}", endpoint, e); throw new WordPressApiException("Failed to execute DELETE request", e); } } /** * Upload file to WordPress media library */ public WordPressMedia uploadMedia(String fileName, byte[] fileData, String mimeType) { try { RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", fileName, RequestBody.create(fileData, MediaType.parse(mimeType))) .build(); Request request = new Request.Builder() .url(baseUrl + apiPrefix + "/media") .post(requestBody) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new WordPressApiException("HTTP " + response.code() + ": " + response.message()); } String responseBody = response.body().string(); return objectMapper.readValue(responseBody, WordPressMedia.class); } } catch (IOException e) { log.error("Media upload failed for file: {}", fileName, e); throw new WordPressApiException("Failed to upload media", e); } } /** * Authenticate with JWT */ public synchronized void authenticateJwt() { if (jwtToken != null && System.currentTimeMillis() < tokenExpiry) { return; // Token is still valid } try { Map<String, String> authRequest = Map.of( "username", authConfig.getJwt().getUsername(), "password", authConfig.getJwt().getPassword() ); String jsonBody = objectMapper.writeValueAsString(authRequest); Request request = new Request.Builder() .url(baseUrl + authConfig.getJwt().getTokenEndpoint()) .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new WordPressApiException("JWT authentication failed: " + response.message()); } String responseBody = response.body().string(); JwtAuthResponse authResponse = objectMapper.readValue(responseBody, JwtAuthResponse.class); jwtToken = authResponse.getToken(); tokenExpiry = System.currentTimeMillis() + (3600 * 1000); // 1 hour expiry log.info("JWT authentication successful for user: {}", authResponse.getUserEmail()); } } catch (IOException e) { log.error("JWT authentication failed", e); throw new WordPressApiException("JWT authentication failed", e); } } /** * Custom interceptors for authentication, retry, and logging */ private class AuthInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder requestBuilder = originalRequest.newBuilder(); // Add appropriate authentication headers switch (authConfig.getType()) { case JWT: authenticateJwt(); requestBuilder.header("Authorization", "Bearer " + jwtToken); break; case BASIC: String credentials = Credentials.basic( authConfig.getBasic().getUsername(), authConfig.getBasic().getPassword() ); requestBuilder.header("Authorization", credentials); break; case API_KEY: requestBuilder.header( authConfig.getApiKey().getHeader(), authConfig.getApiKey().getKey() ); break; case OAUTH1: // OAuth1 implementation would go here break; } return chain.proceed(requestBuilder.build()); } } private class RetryInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = null; IOException exception = null; int maxAttempts = 3; for (int attempt = 1; attempt <= maxAttempts; attempt++) { try { response = chain.proceed(request); if (response.isSuccessful() || !isRetryable(response.code())) { return response; } } catch (IOException e) { exception = e; } if (attempt < maxAttempts) { try { Thread.sleep(1000 * attempt); // Exponential backoff } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("Retry interrupted", e); } } } if (response != null) { return response; } throw exception != null ? exception : new IOException("Unknown error occurred"); } private boolean isRetryable(int statusCode) { return statusCode == 429 || // Too Many Requests statusCode >= 500; // Server errors } } private class LoggingInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); long startTime = System.nanoTime(); log.debug("Sending request: {} {}", request.method(), request.url()); Response response = chain.proceed(request); long elapsedTime = (System.nanoTime() - startTime) / 1_000_000; log.debug("Received response: {} {} in {}ms", response.code(), response.message(), elapsedTime); return response; } } public static class WordPressApiException extends RuntimeException { public WordPressApiException(String message) { super(message); } public WordPressApiException(String message, Throwable cause) { super(message, cause); } } } 2. WordPress Service
// WordPressService.java package com.example.wordpressapi.service; import com.example.wordpressapi.model.*; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Slf4j @Service public class WordPressService { private final WordPressHttpClient httpClient; public WordPressService(WordPressHttpClient httpClient) { this.httpClient = httpClient; } // Post Operations /** * Get all posts with search criteria */ public List<WordPressPost> getPosts(SearchCriteria criteria) { try { Map<String, String> queryParams = buildQueryParams(criteria); WordPressPost[] posts = httpClient.get("/posts", WordPressPost[].class, queryParams); return Arrays.asList(posts); } catch (Exception e) { log.error("Failed to get posts", e); throw e; } } /** * Get post by ID */ public WordPressPost getPost(Long id) { try { return httpClient.get("/posts/" + id, WordPressPost.class, null); } catch (Exception e) { log.error("Failed to get post with ID: {}", id, e); throw e; } } /** * Get post by slug */ public WordPressPost getPostBySlug(String slug) { try { Map<String, String> queryParams = Map.of("slug", slug); WordPressPost[] posts = httpClient.get("/posts", WordPressPost[].class, queryParams); return posts.length > 0 ? posts[0] : null; } catch (Exception e) { log.error("Failed to get post with slug: {}", slug, e); throw e; } } /** * Create new post */ public WordPressPost createPost(CreatePostRequest request) { try { return httpClient.post("/posts", request, WordPressPost.class); } catch (Exception e) { log.error("Failed to create post: {}", request.getTitle(), e); throw e; } } /** * Update existing post */ public WordPressPost updatePost(Long id, UpdatePostRequest request) { try { return httpClient.put("/posts/" + id, request, WordPressPost.class); } catch (Exception e) { log.error("Failed to update post with ID: {}", id, e); throw e; } } /** * Delete post */ public boolean deletePost(Long id) { try { return httpClient.delete("/posts/" + id); } catch (Exception e) { log.error("Failed to delete post with ID: {}", id, e); throw e; } } // Category Operations /** * Get all categories */ public List<WordPressCategory> getCategories() { try { WordPressCategory[] categories = httpClient.get("/categories", WordPressCategory[].class, null); return Arrays.asList(categories); } catch (Exception e) { log.error("Failed to get categories", e); throw e; } } /** * Get category by ID */ public WordPressCategory getCategory(Long id) { try { return httpClient.get("/categories/" + id, WordPressCategory.class, null); } catch (Exception e) { log.error("Failed to get category with ID: {}", id, e); throw e; } } /** * Create new category */ public WordPressCategory createCategory(String name, String description, Long parentId) { try { Map<String, Object> categoryRequest = Map.of( "name", name, "description", description != null ? description : "", "parent", parentId != null ? parentId : 0 ); return httpClient.post("/categories", categoryRequest, WordPressCategory.class); } catch (Exception e) { log.error("Failed to create category: {}", name, e); throw e; } } // Tag Operations /** * Get all tags */ public List<WordPressTag> getTags() { try { WordPressTag[] tags = httpClient.get("/tags", WordPressTag[].class, null); return Arrays.asList(tags); } catch (Exception e) { log.error("Failed to get tags", e); throw e; } } /** * Get tag by ID */ public WordPressTag getTag(Long id) { try { return httpClient.get("/tags/" + id, WordPressTag.class, null); } catch (Exception e) { log.error("Failed to get tag with ID: {}", id, e); throw e; } } // Media Operations /** * Get all media items */ public List<WordPressMedia> getMedia(SearchCriteria criteria) { try { Map<String, String> queryParams = buildQueryParams(criteria); WordPressMedia[] media = httpClient.get("/media", WordPressMedia[].class, queryParams); return Arrays.asList(media); } catch (Exception e) { log.error("Failed to get media", e); throw e; } } /** * Get media by ID */ public WordPressMedia getMedia(Long id) { try { return httpClient.get("/media/" + id, WordPressMedia.class, null); } catch (Exception e) { log.error("Failed to get media with ID: {}", id, e); throw e; } } /** * Upload media file */ public WordPressMedia uploadMedia(String fileName, byte[] fileData, String mimeType) { try { return httpClient.uploadMedia(fileName, fileData, mimeType); } catch (Exception e) { log.error("Failed to upload media: {}", fileName, e); throw e; } } /** * Delete media */ public boolean deleteMedia(Long id) { try { return httpClient.delete("/media/" + id); } catch (Exception e) { log.error("Failed to delete media with ID: {}", id, e); throw e; } } // User Operations /** * Get all users */ public List<WordPressUser> getUsers() { try { WordPressUser[] users = httpClient.get("/users", WordPressUser[].class, null); return Arrays.asList(users); } catch (Exception e) { log.error("Failed to get users", e); throw e; } } /** * Get user by ID */ public WordPressUser getUser(Long id) { try { return httpClient.get("/users/" + id, WordPressUser.class, null); } catch (Exception e) { log.error("Failed to get user with ID: {}", id, e); throw e; } } /** * Get current user (requires authentication) */ public WordPressUser getCurrentUser() { try { return httpClient.get("/users/me", WordPressUser.class, null); } catch (Exception e) { log.error("Failed to get current user", e); throw e; } } // Search and Utility Methods /** * Search content across posts, pages, etc. */ public List<WordPressPost> searchContent(String query, SearchCriteria criteria) { try { Map<String, String> queryParams = buildQueryParams(criteria); queryParams.put("search", query); WordPressPost[] posts = httpClient.get("/posts", WordPressPost[].class, queryParams); return Arrays.asList(posts); } catch (Exception e) { log.error("Failed to search content: {}", query, e); throw e; } } /** * Get posts by category */ public List<WordPressPost> getPostsByCategory(Long categoryId, SearchCriteria criteria) { try { Map<String, String> queryParams = buildQueryParams(criteria); queryParams.put("categories", categoryId.toString()); WordPressPost[] posts = httpClient.get("/posts", WordPressPost[].class, queryParams); return Arrays.asList(posts); } catch (Exception e) { log.error("Failed to get posts by category: {}", categoryId, e); throw e; } } /** * Get posts by tag */ public List<WordPressPost> getPostsByTag(Long tagId, SearchCriteria criteria) { try { Map<String, String> queryParams = buildQueryParams(criteria); queryParams.put("tags", tagId.toString()); WordPressPost[] posts = httpClient.get("/posts", WordPressPost[].class, queryParams); return Arrays.asList(posts); } catch (Exception e) { log.error("Failed to get posts by tag: {}", tagId, e); throw e; } } /** * Build query parameters from search criteria */ private Map<String, String> buildQueryParams(SearchCriteria criteria) { return Map.of( "page", criteria.getPage() != null ? criteria.getPage().toString() : "1", "per_page", criteria.getPerPage() != null ? criteria.getPerPage().toString() : "10", "search", criteria.getSearch() != null ? criteria.getSearch() : "", "orderby", criteria.getOrderBy() != null ? criteria.getOrderBy() : "date", "order", criteria.getOrder() != null ? criteria.getOrder() : "desc", "status", criteria.getStatus() != null ? criteria.getStatus() : "publish" ).entrySet().stream() .filter(entry -> !entry.getValue().isEmpty()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } /** * Health check - test WordPress connection */ public boolean healthCheck() { try { httpClient.get("/posts", Object.class, Map.of("per_page", "1")); return true; } catch (Exception e) { log.warn("WordPress health check failed", e); return false; } } } REST Controllers
1. Posts Controller
// PostController.java package com.example.wordpressapi.controller; import com.example.wordpressapi.model.*; import com.example.wordpressapi.service.WordPressService; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; import java.util.List; @Slf4j @RestController @RequestMapping("/api/wordpress/posts") public class PostController { private final WordPressService wordPressService; public PostController(WordPressService wordPressService) { this.wordPressService = wordPressService; } @GetMapping public ResponseEntity<ApiResponse<List<WordPressPost>>> getPosts( @RequestParam(required = false) Integer page, @RequestParam(required = false) Integer perPage, @RequestParam(required = false) String search, @RequestParam(required = false) String status, @RequestParam(required = false) List<Long> categories, @RequestParam(required = false) List<Long> tags) { try { SearchCriteria criteria = SearchCriteria.builder() .page(page) .perPage(perPage) .search(search) .status(status) .categories(categories) .tags(tags) .build(); List<WordPressPost> posts = wordPressService.getPosts(criteria); return ResponseEntity.ok(ApiResponse.success(posts)); } catch (Exception e) { log.error("Failed to get posts", e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @GetMapping("/{id}") public ResponseEntity<ApiResponse<WordPressPost>> getPost(@PathVariable Long id) { try { WordPressPost post = wordPressService.getPost(id); return ResponseEntity.ok(ApiResponse.success(post)); } catch (Exception e) { log.error("Failed to get post with ID: {}", id, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @GetMapping("/slug/{slug}") public ResponseEntity<ApiResponse<WordPressPost>> getPostBySlug(@PathVariable String slug) { try { WordPressPost post = wordPressService.getPostBySlug(slug); if (post == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(ApiResponse.success(post)); } catch (Exception e) { log.error("Failed to get post with slug: {}", slug, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @PostMapping public ResponseEntity<ApiResponse<WordPressPost>> createPost( @Valid @RequestBody CreatePostRequest request) { try { WordPressPost post = wordPressService.createPost(request); return ResponseEntity.ok(ApiResponse.success(post)); } catch (Exception e) { log.error("Failed to create post: {}", request.getTitle(), e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @PutMapping("/{id}") public ResponseEntity<ApiResponse<WordPressPost>> updatePost( @PathVariable Long id, @Valid @RequestBody UpdatePostRequest request) { try { WordPressPost post = wordPressService.updatePost(id, request); return ResponseEntity.ok(ApiResponse.success(post)); } catch (Exception e) { log.error("Failed to update post with ID: {}", id, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @DeleteMapping("/{id}") public ResponseEntity<ApiResponse<Boolean>> deletePost(@PathVariable Long id) { try { boolean success = wordPressService.deletePost(id); return ResponseEntity.ok(ApiResponse.success(success)); } catch (Exception e) { log.error("Failed to delete post with ID: {}", id, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @GetMapping("/search") public ResponseEntity<ApiResponse<List<WordPressPost>>> searchPosts( @RequestParam String query, @RequestParam(required = false) Integer page, @RequestParam(required = false) Integer perPage) { try { SearchCriteria criteria = SearchCriteria.builder() .page(page) .perPage(perPage) .search(query) .build(); List<WordPressPost> posts = wordPressService.searchContent(query, criteria); return ResponseEntity.ok(ApiResponse.success(posts)); } catch (Exception e) { log.error("Failed to search posts: {}", query, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @GetMapping("/category/{categoryId}") public ResponseEntity<ApiResponse<List<WordPressPost>>> getPostsByCategory( @PathVariable Long categoryId, @RequestParam(required = false) Integer page, @RequestParam(required = false) Integer perPage) { try { SearchCriteria criteria = SearchCriteria.builder() .page(page) .perPage(perPage) .build(); List<WordPressPost> posts = wordPressService.getPostsByCategory(categoryId, criteria); return ResponseEntity.ok(ApiResponse.success(posts)); } catch (Exception e) { log.error("Failed to get posts by category: {}", categoryId, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } } 2. Media Controller
// MediaController.java package com.example.wordpressapi.controller; import com.example.wordpressapi.model.*; import com.example.wordpressapi.service.WordPressService; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.List; @Slf4j @RestController @RequestMapping("/api/wordpress/media") public class MediaController { private final WordPressService wordPressService; public MediaController(WordPressService wordPressService) { this.wordPressService = wordPressService; } @GetMapping public ResponseEntity<ApiResponse<List<WordPressMedia>>> getMedia( @RequestParam(required = false) Integer page, @RequestParam(required = false) Integer perPage) { try { SearchCriteria criteria = SearchCriteria.builder() .page(page) .perPage(perPage) .build(); List<WordPressMedia> media = wordPressService.getMedia(criteria); return ResponseEntity.ok(ApiResponse.success(media)); } catch (Exception e) { log.error("Failed to get media", e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @GetMapping("/{id}") public ResponseEntity<ApiResponse<WordPressMedia>> getMedia(@PathVariable Long id) { try { WordPressMedia media = wordPressService.getMedia(id); return ResponseEntity.ok(ApiResponse.success(media)); } catch (Exception e) { log.error("Failed to get media with ID: {}", id, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<ApiResponse<WordPressMedia>> uploadMedia( @RequestParam("file") MultipartFile file) { try { if (file.isEmpty()) { return ResponseEntity.badRequest().body(ApiResponse.error("File is empty")); } byte[] fileData = file.getBytes(); String fileName = file.getOriginalFilename(); String mimeType = file.getContentType(); WordPressMedia media = wordPressService.uploadMedia(fileName, fileData, mimeType); return ResponseEntity.ok(ApiResponse.success(media)); } catch (IOException e) { log.error("Failed to read file: {}", file.getOriginalFilename(), e); return ResponseEntity.badRequest().body(ApiResponse.error("Failed to read file")); } catch (Exception e) { log.error("Failed to upload media: {}", file.getOriginalFilename(), e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @DeleteMapping("/{id}") public ResponseEntity<ApiResponse<Boolean>> deleteMedia(@PathVariable Long id) { try { boolean success = wordPressService.deleteMedia(id); return ResponseEntity.ok(ApiResponse.success(success)); } catch (Exception e) { log.error("Failed to delete media with ID: {}", id, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } } 3. Categories and Tags Controller
// TaxonomyController.java package com.example.wordpressapi.controller; import com.example.wordpressapi.model.*; import com.example.wordpressapi.service.WordPressService; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @Slf4j @RestController @RequestMapping("/api/wordpress") public class TaxonomyController { private final WordPressService wordPressService; public TaxonomyController(WordPressService wordPressService) { this.wordPressService = wordPressService; } // Categories @GetMapping("/categories") public ResponseEntity<ApiResponse<List<WordPressCategory>>> getCategories() { try { List<WordPressCategory> categories = wordPressService.getCategories(); return ResponseEntity.ok(ApiResponse.success(categories)); } catch (Exception e) { log.error("Failed to get categories", e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @GetMapping("/categories/{id}") public ResponseEntity<ApiResponse<WordPressCategory>> getCategory(@PathVariable Long id) { try { WordPressCategory category = wordPressService.getCategory(id); return ResponseEntity.ok(ApiResponse.success(category)); } catch (Exception e) { log.error("Failed to get category with ID: {}", id, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @PostMapping("/categories") public ResponseEntity<ApiResponse<WordPressCategory>> createCategory( @RequestParam String name, @RequestParam(required = false) String description, @RequestParam(required = false) Long parentId) { try { WordPressCategory category = wordPressService.createCategory(name, description, parentId); return ResponseEntity.ok(ApiResponse.success(category)); } catch (Exception e) { log.error("Failed to create category: {}", name, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } // Tags @GetMapping("/tags") public ResponseEntity<ApiResponse<List<WordPressTag>>> getTags() { try { List<WordPressTag> tags = wordPressService.getTags(); return ResponseEntity.ok(ApiResponse.success(tags)); } catch (Exception e) { log.error("Failed to get tags", e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } @GetMapping("/tags/{id}") public ResponseEntity<ApiResponse<WordPressTag>> getTag(@PathVariable Long id) { try { WordPressTag tag = wordPressService.getTag(id); return ResponseEntity.ok(ApiResponse.success(tag)); } catch (Exception e) { log.error("Failed to get tag with ID: {}", id, e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } } } 4. Health and Status Controller
// StatusController.java package com.example.wordpressapi.controller; import com.example.wordpressapi.model.ApiResponse; import com.example.wordpressapi.service.WordPressService; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @Slf4j @RestController @RequestMapping("/api/wordpress") public class StatusController { private final WordPressService wordPressService; public StatusController(WordPressService wordPressService) { this.wordPressService = wordPressService; } @GetMapping("/health") public ResponseEntity<ApiResponse<Map<String, Object>>> healthCheck() { try { boolean isHealthy = wordPressService.healthCheck(); Map<String, Object> healthInfo = Map.of( "status", isHealthy ? "healthy" : "unhealthy", "timestamp", java.time.LocalDateTime.now(), "service", "wordpress-api-client" ); return ResponseEntity.ok(ApiResponse.success(healthInfo)); } catch (Exception e) { log.error("Health check failed", e); return ResponseEntity.ok(ApiResponse.success(Map.of( "status", "unhealthy", "error", e.getMessage(), "timestamp", java.time.LocalDateTime.now() ))); } } @GetMapping("/status") public ResponseEntity<ApiResponse<Map<String, Object>>> getStatus() { try { // Get current user to verify authentication var currentUser = wordPressService.getCurrentUser(); Map<String, Object> status = Map.of( "connected", true, "authenticated", currentUser != null, "user", currentUser != null ? currentUser.getUsername() : "unknown", "timestamp", java.time.LocalDateTime.now() ); return ResponseEntity.ok(ApiResponse.success(status)); } catch (Exception e) { log.warn("Status check failed", e); return ResponseEntity.ok(ApiResponse.success(Map.of( "connected", false, "error", e.getMessage(), "timestamp", java.time.LocalDateTime.now() ))); } } } Advanced Features
1. Caching Service
// WordPressCacheService.java package com.example.wordpressapi.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j @Service public class WordPressCacheService { private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>(); private final long defaultTtl; private final ScheduledExecutorService cleanupScheduler; public WordPressCacheService(@Value("${app.wordpress.cache.ttl:300}") long defaultTtl) { this.defaultTtl = defaultTtl; this.cleanupScheduler = Executors.newSingleThreadScheduledExecutor(); // Schedule cache cleanup this.cleanupScheduler.scheduleAtFixedRate(this::cleanupExpiredEntries, 60, 60, TimeUnit.SECONDS); } /** * Get value from cache */ public <T> T get(String key) { CacheEntry entry = cache.get(key); if (entry == null) { return null; } if (entry.isExpired()) { cache.remove(key); return null; } @SuppressWarnings("unchecked") T value = (T) entry.getValue(); return value; } /** * Put value in cache */ public void put(String key, Object value) { put(key, value, defaultTtl); } /** * Put value in cache with custom TTL */ public void put(String key, Object value, long ttlSeconds) { CacheEntry entry = new CacheEntry(value, ttlSeconds); cache.put(key, entry); } /** * Remove value from cache */ public void remove(String key) { cache.remove(key); } /** * Clear all cache */ public void clear() { cache.clear(); } /** * Generate cache key for WordPress API calls */ public String generateCacheKey(String endpoint, java.util.Map<String, String> params) { StringBuilder keyBuilder = new StringBuilder("wp_api:"); keyBuilder.append(endpoint); if (params != null && !params.isEmpty()) { params.entrySet().stream() .sorted(java.util.Map.Entry.comparingByKey()) .forEach(entry -> keyBuilder.append(":").append(entry.getKey()).append("=").append(entry.getValue())); } return keyBuilder.toString(); } /** * Clean up expired cache entries */ private void cleanupExpiredEntries() { int initialSize = cache.size(); cache.entrySet().removeIf(entry -> entry.getValue().isExpired()); int finalSize = cache.size(); if (initialSize != finalSize) { log.debug("Cache cleanup completed: removed {} entries", initialSize - finalSize); } } /** * Cache entry with expiration */ private static class CacheEntry { private final Object value; private final LocalDateTime expiryTime; public CacheEntry(Object value, long ttlSeconds) { this.value = value; this.expiryTime = LocalDateTime.now().plusSeconds(ttlSeconds); } public Object getValue() { return value; } public boolean isExpired() { return LocalDateTime.now().isAfter(expiryTime); } } } 2. Batch Operations Service
// WordPressBatchService.java package com.example.wordpressapi.service; import com.example.wordpressapi.model.CreatePostRequest; import com.example.wordpressapi.model.WordPressPost; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Slf4j @Service public class WordPressBatchService { private final WordPressService wordPressService; private final ExecutorService executorService; public WordPressBatchService(WordPressService wordPressService) { this.wordPressService = wordPressService; this.executorService = Executors.newFixedThreadPool(5); } /** * Batch create posts */ public List<WordPressPost> batchCreatePosts(List<CreatePostRequest> posts) { List<CompletableFuture<WordPressPost>> futures = new ArrayList<>(); for (CreatePostRequest post : posts) { CompletableFuture<WordPressPost> future = CompletableFuture.supplyAsync(() -> { try { return wordPressService.createPost(post); } catch (Exception e) { log.error("Failed to create post: {}", post.getTitle(), e); return null; } }, executorService); futures.add(future); } // Wait for all completions CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); // Collect results List<WordPressPost> results = new ArrayList<>(); for (CompletableFuture<WordPressPost> future : futures) { try { WordPressPost result = future.get(); if (result != null) { results.add(result); } } catch (Exception e) { log.error("Error getting batch result", e); } } log.info("Batch create completed: {}/{} successful", results.size(), posts.size()); return results; } /** * Batch update posts */ public List<WordPressPost> batchUpdatePosts(java.util.Map<Long, CreatePostRequest> postUpdates) { List<CompletableFuture<WordPressPost>> futures = new ArrayList<>(); for (java.util.Map.Entry<Long, CreatePostRequest> entry : postUpdates.entrySet()) { CompletableFuture<WordPressPost> future = CompletableFuture.supplyAsync(() -> { try { // Convert CreatePostRequest to UpdatePostRequest CreatePostRequest createRequest = entry.getValue(); com.example.wordpressapi.model.UpdatePostRequest updateRequest = new com.example.wordpressapi.model.UpdatePostRequest(); updateRequest.setTitle(createRequest.getTitle()); updateRequest.setContent(createRequest.getContent()); updateRequest.setExcerpt(createRequest.getExcerpt()); updateRequest.setStatus(createRequest.getStatus()); // ... set other fields return wordPressService.updatePost(entry.getKey(), updateRequest); } catch (Exception e) { log.error("Failed to update post with ID: {}", entry.getKey(), e); return null; } }, executorService); futures.add(future); } CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); List<WordPressPost> results = new ArrayList<>(); for (CompletableFuture<WordPressPost> future : futures) { try { WordPressPost result = future.get(); if (result != null) { results.add(result); } } catch (Exception e) { log.error("Error getting batch update result", e); } } return results; } /** * Batch delete posts */ public BatchDeleteResult batchDeletePosts(List<Long> postIds) { List<CompletableFuture<Boolean>> futures = new ArrayList<>(); for (Long postId : postIds) { CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> { try { return wordPressService.deletePost(postId); } catch (Exception e) { log.error("Failed to delete post with ID: {}", postId, e); return false; } }, executorService); futures.add(future); } CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); int successCount = 0; int failureCount = 0; for (CompletableFuture<Boolean> future : futures) { try { if (future.get()) { successCount++; } else { failureCount++; } } catch (Exception e) { failureCount++; log.error("Error getting batch delete result", e); } } return new BatchDeleteResult(successCount, failureCount); } public static class BatchDeleteResult { private final int successCount; private final int failureCount; public BatchDeleteResult(int successCount, int failureCount) { this.successCount = successCount; this.failureCount = failureCount; } public int getSuccessCount() { return successCount; } public int getFailureCount() { return failureCount; } public int getTotal() { return successCount + failureCount; } } } Testing
1. Unit Tests
// WordPressServiceTest.java package com.example.wordpressapi.service; import com.example.wordpressapi.model.CreatePostRequest; import com.example.wordpressapi.model.WordPressPost; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class WordPressServiceTest { @Mock private WordPressHttpClient httpClient; @InjectMocks private WordPressService wordPressService; @Test void testCreatePost_Success() { // Setup CreatePostRequest request = new CreatePostRequest(); request.setTitle("Test Post"); request.setContent("Test Content"); WordPressPost expectedPost = WordPressPost.builder() .id(1L) .title("Test Post") .content("Test Content") .build(); when(httpClient.post(eq("/posts"), any(CreatePostRequest.class), eq(WordPressPost.class))) .thenReturn(expectedPost); // Execute WordPressPost result = wordPressService.createPost(request); // Verify assertNotNull(result); assertEquals(1L, result.getId()); assertEquals("Test Post", result.getTitle()); verify(httpClient).post(eq("/posts"), any(CreatePostRequest.class), eq(WordPressPost.class)); } @Test void testGetPost_Success() { // Setup WordPressPost expectedPost = WordPressPost.builder() .id(1L) .title("Test Post") .build(); when(httpClient.get(eq("/posts/1"), eq(WordPressPost.class), isNull())) .thenReturn(expectedPost); // Execute WordPressPost result = wordPressService.getPost(1L); // Verify assertNotNull(result); assertEquals(1L, result.getId()); verify(httpClient).get(eq("/posts/1"), eq(WordPressPost.class), isNull()); } } // WordPressHttpClientTest.java package com.example.wordpressapi.service; import com.example.wordpressapi.config.AuthConfig; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import java.io.IOException; import static org.junit.jupiter.api.Assertions.*; class WordPressHttpClientTest { private MockWebServer mockWebServer; private WordPressHttpClient httpClient; @BeforeEach void setUp() throws IOException { mockWebServer = new MockWebServer(); mockWebServer.start(); AuthConfig authConfig = new AuthConfig(); authConfig.setType(AuthConfig.AuthType.NONE); ObjectMapper objectMapper = new ObjectMapper(); httpClient = new WordPressHttpClient( mockWebServer.url("/").toString(), "/wp-json/wp/v2", 30000, authConfig, objectMapper ); } @AfterEach void tearDown() throws IOException { mockWebServer.shutdown(); } @Test void testGet_Success() throws Exception { // Setup String jsonResponse = "{\"id\": 1, \"title\": \"Test Post\"}"; mockWebServer.enqueue(new MockResponse() .setBody(jsonResponse) .setResponseCode(HttpStatus.OK.value()) .addHeader("Content-Type", "application/json")); // Execute TestResponse result = httpClient.get("/posts/1", TestResponse.class, null); // Verify assertNotNull(result); assertEquals(1, result.id); assertEquals("Test Post", result.title); } @Test void testGet_NotFound() { // Setup mockWebServer.enqueue(new MockResponse() .setResponseCode(HttpStatus.NOT_FOUND.value())); // Execute & Verify assertThrows(WordPressHttpClient.WordPressApiException.class, () -> { httpClient.get("/posts/999", TestResponse.class, null); }); } // Test response class static class TestResponse { public int id; public String title; } } 2. Integration Test
// WordPressIntegrationTest.java package com.example.wordpressapi.integration; import com.example.wordpressapi.controller.PostController; import com.example.wordpressapi.model.CreatePostRequest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @ActiveProfiles("test") class WordPressIntegrationTest { @Autowired private PostController postController; @Test void testCreatePost_Integration() { // This would be a real integration test with a test WordPress instance // For demonstration, we'll just verify the controller is properly wired assertNotNull(postController); // Actual integration tests would require a running WordPress instance } } Configuration
// WordPressConfig.java package com.example.wordpressapi.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class WordPressConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); return mapper; } } Error Handling
// GlobalExceptionHandler.java package com.example.wordpressapi.controller; import com.example.wordpressapi.model.ApiResponse; import com.example.wordpressapi.service.WordPressHttpClient; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(WordPressHttpClient.WordPressApiException.class) public ResponseEntity<ApiResponse<Object>> handleWordPressApiException( WordPressHttpClient.WordPressApiException e) { log.error("WordPress API error", e); return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage())); } @ExceptionHandler(Exception.class) public ResponseEntity<ApiResponse<Object>> handleGenericException(Exception e) { log.error("Unexpected error", e); return ResponseEntity.internalServerError() .body(ApiResponse.error("An unexpected error occurred")); } } Best Practices
- Security:
- Use environment variables for credentials
- Implement proper authentication methods
- Validate all inputs
- Use HTTPS for production
- Performance:
- Implement caching for frequently accessed data
- Use connection pooling
- Implement retry mechanisms for transient failures
- Use pagination for large datasets
- Error Handling:
- Comprehensive error logging
- Graceful degradation
- Retry mechanisms for network failures
- User-friendly error messages
- Monitoring:
- Log API request/response times
- Monitor error rates
- Track cache hit ratios
- Set up alerts for service disruptions
Conclusion
This WordPress REST API integration provides:
- Complete CRUD operations for posts, media, categories, and tags
- Multiple authentication methods (JWT, Basic Auth, API Keys)
- Advanced features like caching, batch operations, and search
- Production-ready error handling and monitoring
- Comprehensive testing with unit and integration tests
The solution can be extended with:
- Real-time webhook support
- Advanced search with Elasticsearch integration
- Content synchronization across multiple WordPress instances
- Automated backup and restore operations
- Custom plugin development for extended functionality