Joomla API Integration in Java: Complete Guide

Joomla provides web services through its core API and various extensions. This guide covers both Joomla's core API and custom web service implementations.


1. Joomla API Overview

Joomla Core API Endpoints

- Articles: /api/index.php/v1/content/articles
- Categories: /api/index.php/v1/content/categories
- Users: /api/index.php/v1/users
- Tags: /api/index.php/v1/tags
- Media: /api/index.php/v1/media

Authentication Methods

  • JWT (JSON Web Tokens) - Recommended
  • Basic Authentication
  • API Keys via extensions

2. Project Setup and Dependencies

Maven Dependencies (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>joomla-api-client</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jackson.version>2.15.2</jackson.version>
<okhttp.version>4.11.0</okhttp.version>
<spring.boot.version>2.7.14</spring.boot.version>
<java.jwt.version>4.4.0</java.jwt.version>
</properties>
<dependencies>
<!-- 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>
<!-- JWT Support -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java.jwt.version}</version>
</dependency>
<!-- 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-cache</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>
<!-- Cache -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.6</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
</plugins>
</build>
</project>

3. Configuration Classes

Application Properties (application.yml)

joomla:
api:
base-url: ${JOOMLA_BASE_URL:https://your-joomla-site.com}
username: ${JOOMLA_USERNAME:}
password: ${JOOMLA_PASSWORD:}
api-key: ${JOOMLA_API_KEY:}
timeout: 30000
version: v1
cache:
enabled: true
ttl: 600
retry:
max-attempts: 3
backoff-delay: 1000
logging:
level:
com.example.joomla: DEBUG

Configuration Properties Class

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "joomla.api")
public class JoomlaConfig {
private String baseUrl;
private String username;
private String password;
private String apiKey;
private int timeout = 30000;
private String version = "v1";
private CacheConfig cache = new CacheConfig();
private RetryConfig retry = new RetryConfig();
// Getters and Setters
public String getBaseUrl() { return baseUrl; }
public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getApiKey() { return apiKey; }
public void setApiKey(String apiKey) { this.apiKey = apiKey; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public CacheConfig getCache() { return cache; }
public void setCache(CacheConfig cache) { this.cache = cache; }
public RetryConfig getRetry() { return retry; }
public void setRetry(RetryConfig retry) { this.retry = retry; }
public static class CacheConfig {
private boolean enabled = true;
private long ttl = 600;
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public long getTtl() { return ttl; }
public void setTtl(long ttl) { this.ttl = ttl; }
}
public static class RetryConfig {
private int maxAttempts = 3;
private long backoffDelay = 1000;
public int getMaxAttempts() { return maxAttempts; }
public void setMaxAttempts(int maxAttempts) { this.maxAttempts = maxAttempts; }
public long getBackoffDelay() { return backoffDelay; }
public void setBackoffDelay(long backoffDelay) { this.backoffDelay = backoffDelay; }
}
}

4. Authentication Service

JWT Authentication Service

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class JoomlaAuthService {
private static final Logger logger = LoggerFactory.getLogger(JoomlaAuthService.class);
private final JoomlaConfig config;
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
private String accessToken;
private String refreshToken;
private Date tokenExpiry;
@Autowired
public JoomlaAuthService(JoomlaConfig config, ObjectMapper objectMapper) {
this.config = config;
this.objectMapper = objectMapper;
this.httpClient = new OkHttpClient();
}
public String getAccessToken() throws JoomlaApiException {
if (accessToken == null || isTokenExpired()) {
authenticate();
}
return accessToken;
}
private boolean isTokenExpired() {
return tokenExpiry == null || tokenExpiry.before(new Date());
}
private void authenticate() throws JoomlaApiException {
try {
// Joomla API authentication endpoint
String authUrl = config.getBaseUrl() + "/api/index.php/v1/authentication/token";
// Create form data for authentication
FormBody formBody = new FormBody.Builder()
.add("username", config.getUsername())
.add("password", config.getPassword())
.build();
Request request = new Request.Builder()
.url(authUrl)
.post(formBody)
.header("Content-Type", "application/x-www-form-urlencoded")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new JoomlaApiException("Authentication failed: " + response.code() + " - " + response.message());
}
String responseBody = response.body().string();
AuthResponse authResponse = objectMapper.readValue(responseBody, AuthResponse.class);
if (authResponse.getSuccess() && authResponse.getData() != null) {
this.accessToken = authResponse.getData().getToken();
this.refreshToken = authResponse.getData().getRefreshToken();
// Parse token to get expiry
try {
DecodedJWT jwt = JWT.decode(accessToken);
this.tokenExpiry = jwt.getExpiresAt();
} catch (JWTDecodeException e) {
// If not JWT, set default expiry (1 hour)
this.tokenExpiry = new Date(System.currentTimeMillis() + 3600000);
}
logger.info("Successfully authenticated with Joomla API");
} else {
throw new JoomlaApiException("Authentication failed: " + authResponse.getMessage());
}
}
} catch (IOException e) {
throw new JoomlaApiException("Error during authentication", e);
}
}
public void refreshToken() throws JoomlaApiException {
if (refreshToken == null) {
authenticate();
return;
}
try {
String refreshUrl = config.getBaseUrl() + "/api/index.php/v1/authentication/refresh";
FormBody formBody = new FormBody.Builder()
.add("refresh_token", refreshToken)
.build();
Request request = new Request.Builder()
.url(refreshUrl)
.post(formBody)
.header("Content-Type", "application/x-www-form-urlencoded")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
// If refresh fails, re-authenticate
authenticate();
return;
}
String responseBody = response.body().string();
AuthResponse authResponse = objectMapper.readValue(responseBody, AuthResponse.class);
if (authResponse.getSuccess() && authResponse.getData() != null) {
this.accessToken = authResponse.getData().getToken();
this.refreshToken = authResponse.getData().getRefreshToken();
logger.info("Successfully refreshed Joomla API token");
} else {
authenticate(); // Fallback to full authentication
}
}
} catch (IOException e) {
throw new JoomlaApiException("Error refreshing token", e);
}
}
// DTO for authentication response
public static class AuthResponse {
private boolean success;
private String message;
private AuthData data;
private Map<String, Object> messages;
// Getters and Setters
public boolean getSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public AuthData getData() { return data; }
public void setData(AuthData data) { this.data = data; }
public Map<String, Object> getMessages() { return messages; }
public void setMessages(Map<String, Object> messages) { this.messages = messages; }
}
public static class AuthData {
private String token;
private String refreshToken;
// Getters and Setters
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getRefreshToken() { return refreshToken; }
public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
}
}

5. Data Transfer Objects (DTOs)

Base Response Wrapper

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
@JsonIgnoreProperties(ignoreUnknown = true)
public class JoomlaResponse<T> {
private List<T> data;
private Meta meta;
private Map<String, Object> links;
private Map<String, Object> included;
// Getters and Setters
public List<T> getData() { return data; }
public void setData(List<T> data) { this.data = data; }
public Meta getMeta() { return meta; }
public void setMeta(Meta meta) { this.meta = meta; }
public Map<String, Object> getLinks() { return links; }
public void setLinks(Map<String, Object> links) { this.links = links; }
public Map<String, Object> getIncluded() { return included; }
public void setIncluded(Map<String, Object> included) { this.included = included; }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Meta {
private Pagination pagination;
private Map<String, Object> meta;
public Pagination getPagination() { return pagination; }
public void setPagination(Pagination pagination) { this.pagination = pagination; }
public Map<String, Object> getMeta() { return meta; }
public void setMeta(Map<String, Object> meta) { this.meta = meta; }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Pagination {
private int total;
private int limit;
private int offset;
private int totalPages;
private int currentPage;
@JsonProperty("total") public int getTotal() { return total; }
public void setTotal(int total) { this.total = total; }
@JsonProperty("limit") public int getLimit() { return limit; }
public void setLimit(int limit) { this.limit = limit; }
@JsonProperty("offset") public int getOffset() { return offset; }
public void setOffset(int offset) { this.offset = offset; }
@JsonProperty("total_pages") public int getTotalPages() { return totalPages; }
public void setTotalPages(int totalPages) { this.totalPages = totalPages; }
@JsonProperty("current_page") public int getCurrentPage() { return currentPage; }
public void setCurrentPage(int currentPage) { this.currentPage = currentPage; }
}
}
}

Article DTO

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
import java.util.Map;
@JsonIgnoreProperties(ignoreUnknown = true)
public class JoomlaArticle {
private String id;
private String type;
private ArticleAttributes attributes;
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public ArticleAttributes getAttributes() { return attributes; }
public void setAttributes(ArticleAttributes attributes) { this.attributes = attributes; }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ArticleAttributes {
private String title;
private String alias;
private String introtext;
private String fulltext;
private String state;
private String access;
private String language;
private String metakey;
private String metadesc;
@JsonProperty("created")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime created;
@JsonProperty("created_by")
private String createdBy;
@JsonProperty("created_by_alias")
private String createdByAlias;
@JsonProperty("modified")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modified;
@JsonProperty("modified_by")
private String modifiedBy;
@JsonProperty("publish_up")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime publishUp;
@JsonProperty("publish_down")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime publishDown;
@JsonProperty("featured")
private boolean featured;
@JsonProperty("category_title")
private String categoryTitle;
@JsonProperty("category_alias")
private String categoryAlias;
@JsonProperty("author_name")
private String authorName;
@JsonProperty("author_email")
private String authorEmail;
@JsonProperty("hits")
private int hits;
@JsonProperty("images")
private Map<String, String> images;
@JsonProperty("urls")
private Map<String, String> urls;
@JsonProperty("tags")
private Map<String, Object> tags;
// Getters and Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAlias() { return alias; }
public void setAlias(String alias) { this.alias = alias; }
public String getIntrotext() { return introtext; }
public void setIntrotext(String introtext) { this.introtext = introtext; }
public String getFulltext() { return fulltext; }
public void setFulltext(String fulltext) { this.fulltext = fulltext; }
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getAccess() { return access; }
public void setAccess(String access) { this.access = access; }
public String getLanguage() { return language; }
public void setLanguage(String language) { this.language = language; }
public String getMetakey() { return metakey; }
public void setMetakey(String metakey) { this.metakey = metakey; }
public String getMetadesc() { return metadesc; }
public void setMetadesc(String metadesc) { this.metadesc = metadesc; }
public LocalDateTime getCreated() { return created; }
public void setCreated(LocalDateTime created) { this.created = created; }
public String getCreatedBy() { return createdBy; }
public void setCreatedBy(String createdBy) { this.createdBy = createdBy; }
public String getCreatedByAlias() { return createdByAlias; }
public void setCreatedByAlias(String createdByAlias) { this.createdByAlias = createdByAlias; }
public LocalDateTime getModified() { return modified; }
public void setModified(LocalDateTime modified) { this.modified = modified; }
public String getModifiedBy() { return modifiedBy; }
public void setModifiedBy(String modifiedBy) { this.modifiedBy = modifiedBy; }
public LocalDateTime getPublishUp() { return publishUp; }
public void setPublishUp(LocalDateTime publishUp) { this.publishUp = publishUp; }
public LocalDateTime getPublishDown() { return publishDown; }
public void setPublishDown(LocalDateTime publishDown) { this.publishDown = publishDown; }
public boolean isFeatured() { return featured; }
public void setFeatured(boolean featured) { this.featured = featured; }
public String getCategoryTitle() { return categoryTitle; }
public void setCategoryTitle(String categoryTitle) { this.categoryTitle = categoryTitle; }
public String getCategoryAlias() { return categoryAlias; }
public void setCategoryAlias(String categoryAlias) { this.categoryAlias = categoryAlias; }
public String getAuthorName() { return authorName; }
public void setAuthorName(String authorName) { this.authorName = authorName; }
public String getAuthorEmail() { return authorEmail; }
public void setAuthorEmail(String authorEmail) { this.authorEmail = authorEmail; }
public int getHits() { return hits; }
public void setHits(int hits) { this.hits = hits; }
public Map<String, String> getImages() { return images; }
public void setImages(Map<String, String> images) { this.images = images; }
public Map<String, String> getUrls() { return urls; }
public void setUrls(Map<String, String> urls) { this.urls = urls; }
public Map<String, Object> getTags() { return tags; }
public void setTags(Map<String, Object> tags) { this.tags = tags; }
}
}

Category DTO

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class JoomlaCategory {
private String id;
private String type;
private CategoryAttributes attributes;
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public CategoryAttributes getAttributes() { return attributes; }
public void setAttributes(CategoryAttributes attributes) { this.attributes = attributes; }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class CategoryAttributes {
private String title;
private String alias;
private String description;
private String state;
private String access;
private String language;
private String metakey;
private String metadesc;
@JsonProperty("parent_id")
private String parentId;
@JsonProperty("level")
private int level;
@JsonProperty("lft")
private int left;
@JsonProperty("rgt")
private int right;
@JsonProperty("params")
private String params;
// Getters and Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAlias() { return alias; }
public void setAlias(String alias) { this.alias = alias; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getAccess() { return access; }
public void setAccess(String access) { this.access = access; }
public String getLanguage() { return language; }
public void setLanguage(String language) { this.language = language; }
public String getMetakey() { return metakey; }
public void setMetakey(String metakey) { this.metakey = metakey; }
public String getMetadesc() { return metadesc; }
public void setMetadesc(String metadesc) { this.metadesc = metadesc; }
public String getParentId() { return parentId; }
public void setParentId(String parentId) { this.parentId = parentId; }
public int getLevel() { return level; }
public void setLevel(int level) { this.level = level; }
public int getLeft() { return left; }
public void setLeft(int left) { this.left = left; }
public int getRight() { return right; }
public void setRight(int right) { this.right = right; }
public String getParams() { return params; }
public void setParams(String params) { this.params = params; }
}
}

User DTO

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
@JsonIgnoreProperties(ignoreUnknown = true)
public class JoomlaUser {
private String id;
private String type;
private UserAttributes attributes;
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public UserAttributes getAttributes() { return attributes; }
public void setAttributes(UserAttributes attributes) { this.attributes = attributes; }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class UserAttributes {
private String name;
private String username;
private String email;
private String state;
private String groups;
private String sendEmail;
@JsonProperty("registerDate")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime registerDate;
@JsonProperty("lastvisitDate")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastVisitDate;
@JsonProperty("params")
private String params;
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getGroups() { return groups; }
public void setGroups(String groups) { this.groups = groups; }
public String getSendEmail() { return sendEmail; }
public void setSendEmail(String sendEmail) { this.sendEmail = sendEmail; }
public LocalDateTime getRegisterDate() { return registerDate; }
public void setRegisterDate(LocalDateTime registerDate) { this.registerDate = registerDate; }
public LocalDateTime getLastVisitDate() { return lastVisitDate; }
public void setLastVisitDate(LocalDateTime lastVisitDate) { this.lastVisitDate = lastVisitDate; }
public String getParams() { return params; }
public void setParams(String params) { this.params = params; }
}
}

6. Joomla API Client

Main API Client Service

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
public class JoomlaApiClient {
private static final Logger logger = LoggerFactory.getLogger(JoomlaApiClient.class);
private final JoomlaConfig config;
private final JoomlaAuthService authService;
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
@Autowired
public JoomlaApiClient(JoomlaConfig config, JoomlaAuthService authService, ObjectMapper objectMapper) {
this.config = config;
this.authService = authService;
this.objectMapper = objectMapper;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
.writeTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
.addInterceptor(new RetryInterceptor(config.getRetry().getMaxAttempts(), 
config.getRetry().getBackoffDelay()))
.addInterceptor(new AuthInterceptor(authService))
.build();
}
// Articles Operations
@Cacheable(value = "joomlaArticles", unless = "#result == null || #result.data.isEmpty()")
public JoomlaResponse<JoomlaArticle> getArticles() throws JoomlaApiException {
return getArticles(Collections.emptyMap());
}
@Cacheable(value = "joomlaArticles", key = "{#page, #limit, #filters}", unless = "#result == null || #result.data.isEmpty()")
public JoomlaResponse<JoomlaArticle> getArticles(int page, int limit, Map<String, String> filters) throws JoomlaApiException {
Map<String, String> params = new HashMap<>();
params.put("page[offset]", String.valueOf((page - 1) * limit));
params.put("page[limit]", String.valueOf(limit));
if (filters != null) {
filters.forEach((key, value) -> params.put("filter[" + key + "]", value));
}
return getArticles(params);
}
@Cacheable(value = "joomlaArticlesByCategory", key = "#categoryId", unless = "#result == null || #result.data.isEmpty()")
public JoomlaResponse<JoomlaArticle> getArticlesByCategory(String categoryId) throws JoomlaApiException {
Map<String, String> params = new HashMap<>();
params.put("filter[category_id]", categoryId);
params.put("page[limit]", "50");
return getArticles(params);
}
@Cacheable(value = "joomlaFeaturedArticles", unless = "#result == null || #result.data.isEmpty()")
public JoomlaResponse<JoomlaArticle> getFeaturedArticles() throws JoomlaApiException {
Map<String, String> params = new HashMap<>();
params.put("filter[featured]", "1");
params.put("page[limit]", "20");
return getArticles(params);
}
private JoomlaResponse<JoomlaArticle> getArticles(Map<String, String> queryParams) throws JoomlaApiException {
return makeRequest("/content/articles", queryParams, new TypeReference<JoomlaResponse<JoomlaArticle>>() {});
}
@Cacheable(value = "joomlaArticle", key = "#id", unless = "#result == null")
public JoomlaArticle getArticleById(String id) throws JoomlaApiException {
try {
HttpUrl url = buildUrl("/content/articles/" + id, Collections.emptyMap());
Request request = new Request.Builder().url(url).get().build();
JoomlaResponse<JoomlaArticle> response = executeRequest(request, new TypeReference<JoomlaResponse<JoomlaArticle>>() {});
return response.getData().get(0);
} catch (JoomlaApiException e) {
logger.warn("Failed to get article by ID: {}", id, e);
return null;
}
}
// Create Article
public JoomlaArticle createArticle(CreateArticleRequest articleRequest) throws JoomlaApiException {
try {
String jsonBody = objectMapper.writeValueAsString(articleRequest);
RequestBody body = RequestBody.create(
jsonBody,
MediaType.parse("application/json")
);
HttpUrl url = buildUrl("/content/articles", Collections.emptyMap());
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
JoomlaResponse<JoomlaArticle> response = executeRequest(request, new TypeReference<JoomlaResponse<JoomlaArticle>>() {});
return response.getData().get(0);
} catch (IOException e) {
throw new JoomlaApiException("Error creating article", e);
}
}
// Categories Operations
@Cacheable(value = "joomlaCategories", unless = "#result == null || #result.data.isEmpty()")
public JoomlaResponse<JoomlaCategory> getCategories() throws JoomlaApiException {
return getCategories(Collections.emptyMap());
}
@Cacheable(value = "joomlaCategory", key = "#id", unless = "#result == null")
public JoomlaCategory getCategoryById(String id) throws JoomlaApiException {
try {
HttpUrl url = buildUrl("/content/categories/" + id, Collections.emptyMap());
Request request = new Request.Builder().url(url).get().build();
JoomlaResponse<JoomlaCategory> response = executeRequest(request, new TypeReference<JoomlaResponse<JoomlaCategory>>() {});
return response.getData().get(0);
} catch (JoomlaApiException e) {
logger.warn("Failed to get category by ID: {}", id, e);
return null;
}
}
private JoomlaResponse<JoomlaCategory> getCategories(Map<String, String> queryParams) throws JoomlaApiException {
return makeRequest("/content/categories", queryParams, new TypeReference<JoomlaResponse<JoomlaCategory>>() {});
}
// Users Operations
@Cacheable(value = "joomlaUsers", unless = "#result == null || #result.data.isEmpty()")
public JoomlaResponse<JoomlaUser> getUsers() throws JoomlaApiException {
return getUsers(Collections.emptyMap());
}
@Cacheable(value = "joomlaUser", key = "#id", unless = "#result == null")
public JoomlaUser getUserById(String id) throws JoomlaApiException {
try {
HttpUrl url = buildUrl("/users/" + id, Collections.emptyMap());
Request request = new Request.Builder().url(url).get().build();
JoomlaResponse<JoomlaUser> response = executeRequest(request, new TypeReference<JoomlaResponse<JoomlaUser>>() {});
return response.getData().get(0);
} catch (JoomlaApiException e) {
logger.warn("Failed to get user by ID: {}", id, e);
return null;
}
}
private JoomlaResponse<JoomlaUser> getUsers(Map<String, String> queryParams) throws JoomlaApiException {
return makeRequest("/users", queryParams, new TypeReference<JoomlaResponse<JoomlaUser>>() {});
}
// Generic Request Method
private <T> JoomlaResponse<T> makeRequest(String endpoint, Map<String, String> queryParams, 
TypeReference<JoomlaResponse<T>> typeRef) throws JoomlaApiException {
try {
HttpUrl url = buildUrl(endpoint, queryParams);
Request request = new Request.Builder().url(url).get().build();
return executeRequest(request, typeRef);
} catch (IOException e) {
throw new JoomlaApiException("Error making request to " + endpoint, e);
}
}
private HttpUrl buildUrl(String endpoint, Map<String, String> queryParams) {
HttpUrl.Builder urlBuilder = HttpUrl.parse(config.getBaseUrl() + "/api/index.php/" + 
config.getVersion() + endpoint).newBuilder();
// Add query parameters
queryParams.forEach(urlBuilder::addQueryParameter);
return urlBuilder.build();
}
private <T> T executeRequest(Request request, TypeReference<T> typeRef) throws IOException, JoomlaApiException {
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new JoomlaApiException("HTTP " + response.code() + " - " + response.message());
}
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, typeRef);
}
}
}

Authentication Interceptor

import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class AuthInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
private final JoomlaAuthService authService;
public AuthInterceptor(JoomlaAuthService authService) {
this.authService = authService;
}
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
Request originalRequest = chain.request();
try {
String token = authService.getAccessToken();
Request authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.header("Accept", "application/vnd.api+json")
.build();
return chain.proceed(authenticatedRequest);
} catch (JoomlaApiException e) {
logger.error("Failed to get authentication token", e);
throw new IOException("Authentication failed", e);
}
}
}

Retry Interceptor

import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class RetryInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(RetryInterceptor.class);
private final int maxRetries;
private final long backoffDelay;
public RetryInterceptor(int maxRetries, long backoffDelay) {
this.maxRetries = maxRetries;
this.backoffDelay = backoffDelay;
}
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException exception = null;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
// Only retry on server errors (5xx) or rate limiting
if (response.code() < 500 && response.code() != 429) {
return response;
}
logger.warn("Joomla API request failed with status {} on attempt {}/{}", 
response.code(), attempt, maxRetries);
} catch (IOException e) {
exception = e;
logger.warn("Joomla API request failed with IOException on attempt {}/{}: {}", 
attempt, maxRetries, e.getMessage());
}
if (attempt < maxRetries) {
try {
long delay = backoffDelay * (long) Math.pow(2, attempt - 1);
TimeUnit.MILLISECONDS.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Request interrupted", e);
}
}
if (response != null) {
response.close();
}
}
if (exception != null) {
throw exception;
}
return response;
}
}

7. Create Article Request DTO

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateArticleRequest {
private ArticleData data;
public CreateArticleRequest() {
this.data = new ArticleData();
this.data.setType("articles");
this.data.setAttributes(new ArticleAttributes());
}
// Getters and Setters
public ArticleData getData() { return data; }
public void setData(ArticleData data) { this.data = data; }
// Helper methods
public CreateArticleRequest title(String title) {
this.data.getAttributes().setTitle(title);
return this;
}
public CreateArticleRequest alias(String alias) {
this.data.getAttributes().setAlias(alias);
return this;
}
public CreateArticleRequest introtext(String introtext) {
this.data.getAttributes().setIntrotext(introtext);
return this;
}
public CreateArticleRequest category(String categoryId) {
this.data.getAttributes().setCategoryId(categoryId);
return this;
}
public CreateArticleRequest state(String state) {
this.data.getAttributes().setState(state);
return this;
}
public CreateArticleRequest access(String access) {
this.data.getAttributes().setAccess(access);
return this;
}
public CreateArticleRequest language(String language) {
this.data.getAttributes().setLanguage(language);
return this;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class ArticleData {
private String type;
private ArticleAttributes attributes;
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public ArticleAttributes getAttributes() { return attributes; }
public void setAttributes(ArticleAttributes attributes) { this.attributes = attributes; }
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class ArticleAttributes {
private String title;
private String alias;
private String introtext;
private String fulltext;
private String state;
private String access;
private String language;
private String metakey;
private String metadesc;
@JsonProperty("category_id")
private String categoryId;
@JsonProperty("featured")
private boolean featured;
@JsonProperty("images")
private Map<String, String> images;
@JsonProperty("urls")
private Map<String, String> urls;
// Getters and Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAlias() { return alias; }
public void setAlias(String alias) { this.alias = alias; }
public String getIntrotext() { return introtext; }
public void setIntrotext(String introtext) { this.introtext = introtext; }
public String getFulltext() { return fulltext; }
public void setFulltext(String fulltext) { this.fulltext = fulltext; }
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getAccess() { return access; }
public void setAccess(String access) { this.access = access; }
public String getLanguage() { return language; }
public void setLanguage(String language) { this.language = language; }
public String getMetakey() { return metakey; }
public void setMetakey(String metakey) { this.metakey = metakey; }
public String getMetadesc() { return metadesc; }
public void setMetadesc(String metadesc) { this.metadesc = metadesc; }
public String getCategoryId() { return categoryId; }
public void setCategoryId(String categoryId) { this.categoryId = categoryId; }
public boolean isFeatured() { return featured; }
public void setFeatured(boolean featured) { this.featured = featured; }
public Map<String, String> getImages() { return images; }
public void setImages(Map<String, String> images) { this.images = images; }
public Map<String, String> getUrls() { return urls; }
public void setUrls(Map<String, String> urls) { this.urls = urls; }
}
}

8. Spring Boot Controller

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/joomla")
public class JoomlaApiController {
@Autowired
private JoomlaApiClient joomlaClient;
@GetMapping("/articles")
public ResponseEntity<JoomlaResponse<JoomlaArticle>> getArticles(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int limit) {
try {
JoomlaResponse<JoomlaArticle> response = joomlaClient.getArticles(page, limit, null);
return ResponseEntity.ok(response);
} catch (JoomlaApiException e) {
return ResponseEntity.status(500).body(null);
}
}
@GetMapping("/articles/featured")
public ResponseEntity<JoomlaResponse<JoomlaArticle>> getFeaturedArticles() {
try {
JoomlaResponse<JoomlaArticle> response = joomlaClient.getFeaturedArticles();
return ResponseEntity.ok(response);
} catch (JoomlaApiException e) {
return ResponseEntity.status(500).body(null);
}
}
@GetMapping("/articles/category/{categoryId}")
public ResponseEntity<JoomlaResponse<JoomlaArticle>> getArticlesByCategory(@PathVariable String categoryId) {
try {
JoomlaResponse<JoomlaArticle> response = joomlaClient.getArticlesByCategory(categoryId);
return ResponseEntity.ok(response);
} catch (JoomlaApiException e) {
return ResponseEntity.status(500).body(null);
}
}
@GetMapping("/articles/{id}")
public ResponseEntity<JoomlaArticle> getArticleById(@PathVariable String id) {
try {
JoomlaArticle article = joomlaClient.getArticleById(id);
if (article != null) {
return ResponseEntity.ok(article);
}
return ResponseEntity.notFound().build();
} catch (JoomlaApiException e) {
return ResponseEntity.status(500).body(null);
}
}
@PostMapping("/articles")
public ResponseEntity<JoomlaArticle> createArticle(@RequestBody CreateArticleRequest articleRequest) {
try {
JoomlaArticle article = joomlaClient.createArticle(articleRequest);
return ResponseEntity.ok(article);
} catch (JoomlaApiException e) {
return ResponseEntity.badRequest().body(null);
}
}
@GetMapping("/categories")
public ResponseEntity<JoomlaResponse<JoomlaCategory>> getCategories() {
try {
JoomlaResponse<JoomlaCategory> response = joomlaClient.getCategories();
return ResponseEntity.ok(response);
} catch (JoomlaApiException e) {
return ResponseEntity.status(500).body(null);
}
}
@GetMapping("/categories/{id}")
public ResponseEntity<JoomlaCategory> getCategoryById(@PathVariable String id) {
try {
JoomlaCategory category = joomlaClient.getCategoryById(id);
if (category != null) {
return ResponseEntity.ok(category);
}
return ResponseEntity.notFound().build();
} catch (JoomlaApiException e) {
return ResponseEntity.status(500).body(null);
}
}
@GetMapping("/users/{id}")
public ResponseEntity<JoomlaUser> getUserById(@PathVariable String id) {
try {
JoomlaUser user = joomlaClient.getUserById(id);
if (user != null) {
return ResponseEntity.ok(user);
}
return ResponseEntity.notFound().build();
} catch (JoomlaApiException e) {
return ResponseEntity.status(500).body(null);
}
}
}

9. Custom Exception

public class JoomlaApiException extends Exception {
public JoomlaApiException(String message) {
super(message);
}
public JoomlaApiException(String message, Throwable cause) {
super(message, cause);
}
}

10. Usage Examples

Spring Boot Application

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(JoomlaConfig.class)
public class JoomlaApiApplication {
public static void main(String[] args) {
SpringApplication.run(JoomlaApiApplication.class, args);
}
}

Example Service Usage

@Service
public class JoomlaIntegrationService {
@Autowired
private JoomlaApiClient joomlaClient;
public void demonstrateUsage() {
try {
// Get recent articles
JoomlaResponse<JoomlaArticle> articlesResponse = joomlaClient.getArticles(1, 10, null);
List<JoomlaArticle> articles = articlesResponse.getData();
// Get featured articles
JoomlaResponse<JoomlaArticle> featuredResponse = joomlaClient.getFeaturedArticles();
// Get articles by category
JoomlaResponse<JoomlaArticle> categoryResponse = joomlaClient.getArticlesByCategory("5");
// Create a new article
CreateArticleRequest newArticle = new CreateArticleRequest()
.title("New Article from Java API")
.alias("new-article-from-java-api")
.introtext("This article was created via Joomla API from Java")
.category("2")
.state("1") // Published
.access("1") // Public
.language("*"); // All languages
JoomlaArticle createdArticle = joomlaClient.createArticle(newArticle);
System.out.println("Created article with ID: " + createdArticle.getId());
// Get categories
JoomlaResponse<JoomlaCategory> categoriesResponse = joomlaClient.getCategories();
List<JoomlaCategory> categories = categoriesResponse.getData();
} catch (JoomlaApiException e) {
e.printStackTrace();
}
}
}

This comprehensive Java client for Joomla API provides complete functionality for interacting with Joomla content, including articles, categories, and users, with proper JWT authentication, caching, and error handling.

Leave a Reply

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


Macro Nepal Helper