WordPress REST API Integration in Java: Complete Implementation Guide

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

  1. Security:
  • Use environment variables for credentials
  • Implement proper authentication methods
  • Validate all inputs
  • Use HTTPS for production
  1. Performance:
  • Implement caching for frequently accessed data
  • Use connection pooling
  • Implement retry mechanisms for transient failures
  • Use pagination for large datasets
  1. Error Handling:
  • Comprehensive error logging
  • Graceful degradation
  • Retry mechanisms for network failures
  • User-friendly error messages
  1. 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

Leave a Reply

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


Macro Nepal Helper