ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. This guide covers how to programmatically manage and sync ArgoCD applications using Java.
Setup and Dependencies
1. Maven Dependencies
<properties> <kubernetes-client.version>6.7.2</kubernetes-client.version> <argo-cd.version>2.7.0</argo-cd.version> <jackson.version>2.15.2</jackson.version> <okhttp.version>4.11.0</okhttp.version> </properties> <dependencies> <!-- Argo CD Models --> <dependency> <groupId>io.argoproj</groupId> <artifactId>argo-cd-client</artifactId> <version>${argo-cd.version}</version> </dependency> <!-- Kubernetes Client --> <dependency> <groupId>io.kubernetes</groupId> <artifactId>client-java</artifactId> <version>${kubernetes-client.version}</version> <classifier>jar</classifier> </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> <!-- YAML Processing --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>${jackson.version}</version> </dependency> </dependencies> 2. Configuration Properties
@Configuration @ConfigurationProperties(prefix = "argocd") @Data public class ArgoCDConfig { private String serverUrl; private String authToken; private String username; private String password; private boolean insecure = false; private long timeoutMs = 30000; private String namespace = "argocd"; // Sync options private boolean prune = true; private boolean dryRun = false; private String strategy = "apply"; private boolean force = false; // Retry configuration private int maxRetries = 3; private long retryDelayMs = 5000; } # application.yml argocd: server-url: ${ARGOCD_SERVER:https://argocd.example.com} auth-token: ${ARGOCD_TOKEN:} username: ${ARGOCD_USERNAME:admin} password: ${ARGOCD_PASSWORD:} insecure: false timeout-ms: 30000 namespace: argocd prune: true dry-run: false strategy: apply force: false max-retries: 3 retry-delay-ms: 5000 Core ArgoCD Client
1. ArgoCD Client Factory
@Component @Slf4j public class ArgoCDClientFactory { private final ArgoCDConfig config; private OkHttpClient httpClient; private ObjectMapper objectMapper; public ArgoCDClientFactory(ArgoCDConfig config) { this.config = config; initializeHttpClient(); initializeObjectMapper(); } private void initializeHttpClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(Duration.ofMillis(config.getTimeoutMs())) .readTimeout(Duration.ofMillis(config.getTimeoutMs())) .writeTimeout(Duration.ofMillis(config.getTimeoutMs())); // Add authentication interceptor builder.addInterceptor(new ArgoCDAuthInterceptor(config)); // Add logging interceptor if (log.isDebugEnabled()) { builder.addInterceptor(new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY)); } // Handle insecure SSL if needed if (config.isInsecure()) { builder.sslSocketFactory(InsecureSSLFactory.getSocketFactory(), InsecureSSLFactory.getTrustManager()); builder.hostnameVerifier((hostname, session) -> true); } this.httpClient = builder.build(); } private void initializeObjectMapper() { this.objectMapper = new ObjectMapper(); this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); this.objectMapper.registerModule(new JavaTimeModule()); } public ArgoCDRestClient createRestClient() { return new ArgoCDRestClient(httpClient, objectMapper, config); } public ArgoCDKubernetesClient createKubernetesClient() throws IOException { return new ArgoCDKubernetesClient(config); } @PreDestroy public void close() { if (httpClient != null) { httpClient.dispatcher().executorService().shutdown(); httpClient.connectionPool().evictAll(); } } } // Authentication Interceptor @Slf4j class ArgoCDAuthInterceptor implements Interceptor { private final ArgoCDConfig config; public ArgoCDAuthInterceptor(ArgoCDConfig config) { this.config = config; } @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder(); // Prefer token authentication if (config.getAuthToken() != null && !config.getAuthToken().trim().isEmpty()) { builder.header("Authorization", "Bearer " + config.getAuthToken()); } // Fallback to basic auth else if (config.getUsername() != null && config.getPassword() != null) { String credentials = config.getUsername() + ":" + config.getPassword(); String basicAuth = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes()); builder.header("Authorization", basicAuth); } builder.header("Content-Type", "application/json"); builder.header("Accept", "application/json"); return chain.proceed(builder.build()); } } 2. REST Client Implementation
@Component @Slf4j public class ArgoCDRestClient { private final OkHttpClient httpClient; private final ObjectMapper objectMapper; private final ArgoCDConfig config; private final String baseUrl; public ArgoCDRestClient(OkHttpClient httpClient, ObjectMapper objectMapper, ArgoCDConfig config) { this.httpClient = httpClient; this.objectMapper = objectMapper; this.config = config; this.baseUrl = config.getServerUrl() + "/api/v1"; } // Application Management public List<Application> getApplications() throws ArgoCDException { return executeGet("/applications", new TypeReference<List<Application>>() {}); } public Application getApplication(String name) throws ArgoCDException { return executeGet("/applications/" + name, Application.class); } public Application createApplication(Application application) throws ArgoCDException { String json = toJson(application); return executePost("/applications", json, Application.class); } public Application updateApplication(String name, Application application) throws ArgoCDException { String json = toJson(application); return executePut("/applications/" + name, json, Application.class); } public void deleteApplication(String name) throws ArgoCDException { executeDelete("/applications/" + name); } // Application Sync public SyncOperationResult syncApplication(String applicationName, SyncOperation syncOperation) throws ArgoCDException { String json = toJson(syncOperation); return executePost("/applications/" + applicationName + "/sync", json, SyncOperationResult.class); } public Application watchApplication(String name, long timeoutSeconds) throws ArgoCDException { String url = String.format("/applications/%s?watch=true&timeoutSeconds=%d", name, timeoutSeconds); return executeGet(url, Application.class); } // Sync Status and Operations public List<OperationState> getOperationHistory(String applicationName) throws ArgoCDException { return executeGet("/applications/" + applicationName + "/operations", new TypeReference<List<OperationState>>() {}); } public SyncOperationResult getSyncResult(String applicationName, String operationId) throws ArgoCDException { return executeGet("/applications/" + applicationName + "/operations/" + operationId, SyncOperationResult.class); } // Resource Actions public Object getResource(String applicationName, String namespace, String resourceName, String version, String kind) throws ArgoCDException { String url = String.format("/applications/%s/resource?namespace=%s&name=%s&version=%s&kind=%s", applicationName, namespace, resourceName, version, kind); return executeGet(url, Object.class); } public Object patchResource(String applicationName, String namespace, String resourceName, String version, String kind, String patchType, Object patch) throws ArgoCDException { String url = String.format("/applications/%s/resource?namespace=%s&name=%s&version=%s&kind=%s", applicationName, namespace, resourceName, version, kind); String jsonPatch = toJson(patch); return executePost(url + "&patchType=" + patchType, jsonPatch, Object.class); } // Private HTTP methods private <T> T executeGet(String path, Class<T> responseType) throws ArgoCDException { return executeRequest(new Request.Builder().url(baseUrl + path).get(), responseType); } private <T> T executeGet(String path, TypeReference<T> typeReference) throws ArgoCDException { return executeRequest(new Request.Builder().url(baseUrl + path).get(), typeReference); } private <T> T executePost(String path, String jsonBody, Class<T> responseType) throws ArgoCDException { RequestBody body = RequestBody.create(jsonBody, MediaType.parse("application/json")); return executeRequest(new Request.Builder().url(baseUrl + path).post(body), responseType); } private <T> T executePut(String path, String jsonBody, Class<T> responseType) throws ArgoCDException { RequestBody body = RequestBody.create(jsonBody, MediaType.parse("application/json")); return executeRequest(new Request.Builder().url(baseUrl + path).put(body), responseType); } private void executeDelete(String path) throws ArgoCDException { executeRequest(new Request.Builder().url(baseUrl + path).delete(), Void.class); } private <T> T executeRequest(Request.Builder requestBuilder, Class<T> responseType) throws ArgoCDException { try { Response response = httpClient.newCall(requestBuilder.build()).execute(); return handleResponse(response, responseType); } catch (IOException e) { throw new ArgoCDException("HTTP request failed", e); } } private <T> T executeRequest(Request.Builder requestBuilder, TypeReference<T> typeReference) throws ArgoCDException { try { Response response = httpClient.newCall(requestBuilder.build()).execute(); return handleResponse(response, typeReference); } catch (IOException e) { throw new ArgoCDException("HTTP request failed", e); } } private <T> T handleResponse(Response response, Class<T> responseType) throws ArgoCDException { try { if (!response.isSuccessful()) { throw new ArgoCDException("HTTP " + response.code() + ": " + response.message()); } if (responseType == Void.class) { return null; } String responseBody = response.body().string(); return objectMapper.readValue(responseBody, responseType); } catch (IOException e) { throw new ArgoCDException("Failed to parse response", e); } } private <T> T handleResponse(Response response, TypeReference<T> typeReference) throws ArgoCDException { try { if (!response.isSuccessful()) { throw new ArgoCDException("HTTP " + response.code() + ": " + response.message()); } String responseBody = response.body().string(); return objectMapper.readValue(responseBody, typeReference); } catch (IOException e) { throw new ArgoCDException("Failed to parse response", e); } } private String toJson(Object object) throws ArgoCDException { try { return objectMapper.writeValueAsString(object); } catch (JsonProcessingException e) { throw new ArgoCDException("Failed to serialize object to JSON", e); } } } public class ArgoCDException extends Exception { public ArgoCDException(String message) { super(message); } public ArgoCDException(String message, Throwable cause) { super(message, cause); } } 3. Kubernetes Client Implementation
@Component @Slf4j public class ArgoCDKubernetesClient { private final ApiClient apiClient; private final CustomObjectsApi customObjectsApi; private final String namespace; public ArgoCDKubernetesClient(ArgoCDConfig config) throws IOException { this.namespace = config.getNamespace(); // Load kubeconfig or use in-cluster config if (config.getAuthToken() != null) { this.apiClient = createApiClientFromToken(config); } else { this.apiClient = Config.defaultClient(); } if (config.isInsecure()) { apiClient.setVerifyingSsl(false); } this.customObjectsApi = new CustomObjectsApi(apiClient); } private ApiClient createApiClientFromToken(ArgoCDConfig config) { ApiClient client = new ApiClient(); client.setBasePath(config.getServerUrl()); client.setApiKey("Bearer " + config.getAuthToken()); client.setConnectTimeout(config.getTimeoutMs()); client.setReadTimeout(config.getTimeoutMs()); client.setWriteTimeout(config.getTimeoutMs()); return client; } public Object getApplication(String name) throws ArgoCDException { try { return customObjectsApi.getNamespacedCustomObject( "argoproj.io", "v1alpha1", namespace, "applications", name ); } catch (ApiException e) { throw new ArgoCDException("Failed to get application: " + name, e); } } public Object createApplication(Object application) throws ArgoCDException { try { return customObjectsApi.createNamespacedCustomObject( "argoproj.io", "v1alpha1", namespace, "applications", application, null, null, null ); } catch (ApiException e) { throw new ArgoCDException("Failed to create application", e); } } public Object patchApplication(String name, Object patch) throws ArgoCDException { try { return customObjectsApi.patchNamespacedCustomObject( "argoproj.io", "v1alpha1", namespace, "applications", name, patch, null, null, null ); } catch (ApiException e) { throw new ArgoCDException("Failed to patch application: " + name, e); } } public void deleteApplication(String name) throws ArgoCDException { try { customObjectsApi.deleteNamespacedCustomObject( "argoproj.io", "v1alpha1", namespace, "applications", name, null, null, null, null, null, null ); } catch (ApiException e) { throw new ArgoCDException("Failed to delete application: " + name, e); } } public List<Object> listApplications() throws ArgoCDException { try { Object response = customObjectsApi.listNamespacedCustomObject( "argoproj.io", "v1alpha1", namespace, "applications", null, null, null, null, null, null, null, null ); return extractItems(response); } catch (ApiException e) { throw new ArgoCDException("Failed to list applications", e); } } @SuppressWarnings("unchecked") private List<Object> extractItems(Object response) { if (response instanceof Map) { Map<String, Object> responseMap = (Map<String, Object>) response; Object items = responseMap.get("items"); if (items instanceof List) { return (List<Object>) items; } } return Collections.emptyList(); } } Application Sync Service
1. Core Sync Service
@Service @Slf4j public class ArgoCDSyncService { private final ArgoCDRestClient restClient; private final ArgoCDConfig config; private final ObjectMapper objectMapper; public ArgoCDSyncService(ArgoCDRestClient restClient, ArgoCDConfig config, ObjectMapper objectMapper) { this.restClient = restClient; this.config = config; this.objectMapper = objectMapper; } public SyncOperationResult syncApplication(String applicationName) throws ArgoCDException { return syncApplication(applicationName, createDefaultSyncOperation()); } public SyncOperationResult syncApplication(String applicationName, SyncOperation syncOperation) throws ArgoCDException { log.info("Initiating sync for application: {}", applicationName); SyncOperationResult result = restClient.syncApplication(applicationName, syncOperation); if (result != null && result.getStarted() != null) { log.info("Sync started for application: {} with operation ID: {}", applicationName, result.getStarted().getOperation().getSync().getRevision()); // Wait for sync completion if not dry run if (!config.isDryRun() && syncOperation.getDryRun() == null || !syncOperation.getDryRun()) { return waitForSyncCompletion(applicationName, result); } } return result; } public SyncOperationResult syncApplicationWithRetry(String applicationName, int maxRetries) throws ArgoCDException { return syncApplicationWithRetry(applicationName, createDefaultSyncOperation(), maxRetries); } public SyncOperationResult syncApplicationWithRetry(String applicationName, SyncOperation syncOperation, int maxRetries) throws ArgoCDException { ArgoCDException lastException = null; for (int attempt = 1; attempt <= maxRetries; attempt++) { try { log.info("Sync attempt {}/{} for application: {}", attempt, maxRetries, applicationName); return syncApplication(applicationName, syncOperation); } catch (ArgoCDException e) { lastException = e; log.warn("Sync attempt {}/{} failed for application: {} - {}", attempt, maxRetries, applicationName, e.getMessage()); if (attempt < maxRetries) { try { Thread.sleep(config.getRetryDelayMs()); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new ArgoCDException("Sync interrupted", ie); } } } } throw new ArgoCDException("All sync attempts failed for application: " + applicationName, lastException); } public SyncOperationResult syncAndWait(String applicationName, long timeoutMinutes) throws ArgoCDException { SyncOperationResult result = syncApplication(applicationName); return waitForSyncCompletion(applicationName, result, timeoutMinutes); } public boolean isApplicationSynced(String applicationName) throws ArgoCDException { Application application = restClient.getApplication(applicationName); return application.getStatus() != null && "Synced".equals(application.getStatus().getSync().getStatus()); } public ApplicationHealth getApplicationHealth(String applicationName) throws ArgoCDException { Application application = restClient.getApplication(applicationName); return application.getStatus() != null ? application.getStatus().getHealth() : null; } public List<ResourceStatus> getOutOfSyncResources(String applicationName) throws ArgoCDException { Application application = restClient.getApplication(applicationName); if (application.getStatus() == null || application.getStatus().getResources() == null) { return Collections.emptyList(); } return application.getStatus().getResources().stream() .filter(resource -> resource.getStatus() != null && "OutOfSync".equals(resource.getStatus())) .collect(Collectors.toList()); } // Private methods private SyncOperation createDefaultSyncOperation() { SyncOperation syncOperation = new SyncOperation(); syncOperation.setPrune(config.isPrune()); syncOperation.setDryRun(config.isDryRun()); syncOperation.setStrategy(createSyncStrategy()); List<SyncOperationResource> resources = new ArrayList<>(); syncOperation.setResources(resources); return syncOperation; } private SyncStrategy createSyncStrategy() { SyncStrategy strategy = new SyncStrategy(); if ("hook".equals(config.getStrategy())) { SyncStrategyHook hook = new SyncStrategyHook(); hook.setForce(config.isForce()); strategy.setHook(hook); } else { SyncStrategyApply apply = new SyncStrategyApply(); apply.setForce(config.isForce()); strategy.setApply(apply); } return strategy; } private SyncOperationResult waitForSyncCompletion(String applicationName, SyncOperationResult initialResult) throws ArgoCDException { return waitForSyncCompletion(applicationName, initialResult, 10); // Default 10 minutes timeout } private SyncOperationResult waitForSyncCompletion(String applicationName, SyncOperationResult initialResult, long timeoutMinutes) throws ArgoCDException { long startTime = System.currentTimeMillis(); long timeoutMs = timeoutMinutes * 60 * 1000; log.info("Waiting for sync completion for application: {} (timeout: {} minutes)", applicationName, timeoutMinutes); while (System.currentTimeMillis() - startTime < timeoutMs) { try { Application application = restClient.getApplication(applicationName); ApplicationStatus status = application.getStatus(); if (status != null && status.getOperationState() != null) { OperationState operationState = status.getOperationState(); String phase = operationState.getPhase(); log.debug("Sync phase for application {}: {}", applicationName, phase); if ("Succeeded".equals(phase)) { log.info("Sync completed successfully for application: {}", applicationName); SyncOperationResult result = new SyncOperationResult(); result.setCompleted(operationState); return result; } else if ("Failed".equals(phase) || "Error".equals(phase)) { log.error("Sync failed for application: {} - {}", applicationName, operationState.getMessage()); throw new ArgoCDException("Sync failed: " + operationState.getMessage()); } // Continue waiting for "Running" phase } // Wait before next check Thread.sleep(5000); // 5 seconds } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ArgoCDException("Sync wait interrupted", e); } } throw new ArgoCDException("Sync timeout after " + timeoutMinutes + " minutes for application: " + applicationName); } } 2. Application Management Service
@Service @Slf4j public class ArgoCDApplicationService { private final ArgoCDRestClient restClient; private final ArgoCDKubernetesClient k8sClient; public ArgoCDApplicationService(ArgoCDRestClient restClient, ArgoCDKubernetesClient k8sClient) { this.restClient = restClient; this.k8sClient = k8sClient; } public Application createApplicationFromManifest(String name, String namespace, String repoUrl, String path, String targetRevision) throws ArgoCDException { Application application = new Application(); application.setMetadata(createMetadata(name, namespace)); application.setSpec(createApplicationSpec(repoUrl, path, targetRevision, namespace)); return restClient.createApplication(application); } public Application createApplicationFromYaml(String yamlContent) throws ArgoCDException { try { ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); Application application = yamlMapper.readValue(yamlContent, Application.class); return restClient.createApplication(application); } catch (IOException e) { throw new ArgoCDException("Failed to parse YAML application manifest", e); } } public List<Application> getApplicationsByProject(String project) throws ArgoCDException { List<Application> allApplications = restClient.getApplications(); return allApplications.stream() .filter(app -> app.getSpec() != null && project.equals(app.getSpec().getProject())) .collect(Collectors.toList()); } public List<Application> getOutOfSyncApplications() throws ArgoCDException { List<Application> allApplications = restClient.getApplications(); return allApplications.stream() .filter(app -> app.getStatus() != null && "OutOfSync".equals(app.getStatus().getSync().getStatus())) .collect(Collectors.toList()); } public List<Application> getUnhealthyApplications() throws ArgoCDException { List<Application> allApplications = restClient.getApplications(); return allApplications.stream() .filter(app -> app.getStatus() != null && app.getStatus().getHealth() != null && !"Healthy".equals(app.getStatus().getHealth().getStatus())) .collect(Collectors.toList()); } public void refreshApplication(String applicationName) throws ArgoCDException { // Refresh by doing a no-op sync SyncOperation syncOperation = new SyncOperation(); syncOperation.setDryRun(true); syncOperation.setPrune(false); restClient.syncApplication(applicationName, syncOperation); log.info("Application refreshed: {}", applicationName); } public Application updateApplicationTargetRevision(String applicationName, String newRevision) throws ArgoCDException { Application application = restClient.getApplication(applicationName); if (application.getSpec() != null && application.getSpec().getSource() != null) { application.getSpec().getSource().setTargetRevision(newRevision); return restClient.updateApplication(applicationName, application); } throw new ArgoCDException("Cannot update target revision for application: " + applicationName); } // Helper methods private ObjectMeta createMetadata(String name, String namespace) { ObjectMeta metadata = new ObjectMeta(); metadata.setName(name); metadata.setNamespace(namespace); return metadata; } private ApplicationSpec createApplicationSpec(String repoUrl, String path, String targetRevision, String destinationNamespace) { ApplicationSpec spec = new ApplicationSpec(); // Source ApplicationSource source = new ApplicationSource(); source.setRepoURL(repoUrl); source.setPath(path); source.setTargetRevision(targetRevision); spec.setSource(source); // Destination ApplicationDestination destination = new ApplicationDestination(); destination.setServer("https://kubernetes.default.svc"); destination.setNamespace(destinationNamespace); spec.setDestination(destination); // Project spec.setProject("default"); // Sync policy SyncPolicy syncPolicy = new SyncPolicy(); SyncPolicyAutomated automated = new SyncPolicyAutomated(); automated.setPrune(true); automated.setSelfHeal(true); syncPolicy.setAutomated(automated); spec.setSyncPolicy(syncPolicy); return spec; } } Domain Models
1. ArgoCD Application Models
// Simplified ArgoCD Application model @Data public class Application { private String apiVersion = "argoproj.io/v1alpha1"; private String kind = "Application"; private ObjectMeta metadata; private ApplicationSpec spec; private ApplicationStatus status; } @Data public class ObjectMeta { private String name; private String namespace; private Map<String, String> labels; private Map<String, String> annotations; } @Data public class ApplicationSpec { private ApplicationSource source; private ApplicationDestination destination; private String project; private SyncPolicy syncPolicy; } @Data public class ApplicationSource { private String repoURL; private String path; private String targetRevision; private String chart; private String valueFiles; } @Data public class ApplicationDestination { private String server; private String namespace; } @Data public class SyncPolicy { private SyncPolicyAutomated automated; private SyncPolicyRetry retry; } @Data public class SyncPolicyAutomated { private boolean prune; private boolean selfHeal; } @Data public class ApplicationStatus { private List<ResourceStatus> resources; private SyncStatus sync; private ApplicationHealth health; private OperationState operationState; private String history; } @Data public class ResourceStatus { private String name; private String kind; private String namespace; private String status; private String health; } @Data public class SyncStatus { private String status; private String revision; } @Data public class ApplicationHealth { private String status; private String message; } @Data public class OperationState { private String phase; private String message; private Operation operation; private SyncOperationResult syncResult; } @Data public class SyncOperation { private String revision; private Boolean prune; private Boolean dryRun; private SyncStrategy strategy; private List<SyncOperationResource> resources; private List<String> syncOptions; } @Data public class SyncOperationResult { private Operation started; private OperationState completed; } @Data public class Operation { private SyncOperation sync; } // Additional models as needed... REST Controllers
1. Sync Controller
@RestController @RequestMapping("/api/argocd") @Slf4j public class ArgoCDSyncController { private final ArgoCDSyncService syncService; private final ArgoCDApplicationService appService; public ArgoCDSyncController(ArgoCDSyncService syncService, ArgoCDApplicationService appService) { this.syncService = syncService; this.appService = appService; } @PostMapping("/applications/{name}/sync") public ResponseEntity<SyncResponse> syncApplication(@PathVariable String name) { try { SyncOperationResult result = syncService.syncApplication(name); return ResponseEntity.ok(new SyncResponse("Sync initiated", result)); } catch (ArgoCDException e) { log.error("Failed to sync application: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new SyncResponse("Sync failed: " + e.getMessage(), null)); } } @PostMapping("/applications/{name}/sync-and-wait") public ResponseEntity<SyncResponse> syncAndWait(@PathVariable String name, @RequestParam(defaultValue = "10") long timeoutMinutes) { try { SyncOperationResult result = syncService.syncAndWait(name, timeoutMinutes); return ResponseEntity.ok(new SyncResponse("Sync completed", result)); } catch (ArgoCDException e) { log.error("Failed to sync application: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new SyncResponse("Sync failed: " + e.getMessage(), null)); } } @GetMapping("/applications/{name}/sync-status") public ResponseEntity<SyncStatusResponse> getSyncStatus(@PathVariable String name) { try { boolean isSynced = syncService.isApplicationSynced(name); ApplicationHealth health = syncService.getApplicationHealth(name); List<ResourceStatus> outOfSyncResources = syncService.getOutOfSyncResources(name); SyncStatusResponse response = new SyncStatusResponse(isSynced, health, outOfSyncResources); return ResponseEntity.ok(response); } catch (ArgoCDException e) { log.error("Failed to get sync status for application: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping("/applications/out-of-sync") public ResponseEntity<List<Application>> getOutOfSyncApplications() { try { List<Application> applications = appService.getOutOfSyncApplications(); return ResponseEntity.ok(applications); } catch (ArgoCDException e) { log.error("Failed to get out-of-sync applications", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } // Response DTOs @Data @AllArgsConstructor public static class SyncResponse { private String message; private SyncOperationResult result; } @Data @AllArgsConstructor public static class SyncStatusResponse { private boolean synced; private ApplicationHealth health; private List<ResourceStatus> outOfSyncResources; } } 2. Application Management Controller
@RestController @RequestMapping("/api/argocd/applications") @Slf4j public class ApplicationController { private final ArgoCDApplicationService appService; private final ArgoCDRestClient restClient; public ApplicationController(ArgoCDApplicationService appService, ArgoCDRestClient restClient) { this.appService = appService; this.restClient = restClient; } @PostMapping public ResponseEntity<Application> createApplication(@RequestBody CreateApplicationRequest request) { try { Application application = appService.createApplicationFromManifest( request.getName(), request.getNamespace(), request.getRepoUrl(), request.getPath(), request.getTargetRevision() ); return ResponseEntity.status(HttpStatus.CREATED).body(application); } catch (ArgoCDException e) { log.error("Failed to create application", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/yaml") public ResponseEntity<Application> createApplicationFromYaml(@RequestBody String yamlContent) { try { Application application = appService.createApplicationFromYaml(yamlContent); return ResponseEntity.status(HttpStatus.CREATED).body(application); } catch (ArgoCDException e) { log.error("Failed to create application from YAML", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping public ResponseEntity<List<Application>> getApplications() { try { List<Application> applications = restClient.getApplications(); return ResponseEntity.ok(applications); } catch (ArgoCDException e) { log.error("Failed to get applications", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping("/{name}") public ResponseEntity<Application> getApplication(@PathVariable String name) { try { Application application = restClient.getApplication(name); return ResponseEntity.ok(application); } catch (ArgoCDException e) { log.error("Failed to get application: {}", name, e); return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } } @DeleteMapping("/{name}") public ResponseEntity<Void> deleteApplication(@PathVariable String name) { try { restClient.deleteApplication(name); return ResponseEntity.noContent().build(); } catch (ArgoCDException e) { log.error("Failed to delete application: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PutMapping("/{name}/revision") public ResponseEntity<Application> updateTargetRevision(@PathVariable String name, @RequestParam String revision) { try { Application application = appService.updateApplicationTargetRevision(name, revision); return ResponseEntity.ok(application); } catch (ArgoCDException e) { log.error("Failed to update target revision for application: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/{name}/refresh") public ResponseEntity<Void> refreshApplication(@PathVariable String name) { try { appService.refreshApplication(name); return ResponseEntity.ok().build(); } catch (ArgoCDException e) { log.error("Failed to refresh application: {}", name, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } // Request DTO @Data public static class CreateApplicationRequest { @NotBlank private String name; @NotBlank private String namespace; @NotBlank private String repoUrl; @NotBlank private String path; private String targetRevision = "HEAD"; } } Scheduled Sync Service
@Service @Slf4j public class ScheduledSyncService { private final ArgoCDSyncService syncService; private final ArgoCDApplicationService appService; public ScheduledSyncService(ArgoCDSyncService syncService, ArgoCDApplicationService appService) { this.syncService = syncService; this.appService = appService; } @Scheduled(cron = "${argocd.sync.cron:0 0 * * * *}") // Every hour by default public void syncOutOfSyncApplications() { log.info("Starting scheduled sync of out-of-sync applications"); try { List<Application> outOfSyncApps = appService.getOutOfSyncApplications(); log.info("Found {} out-of-sync applications", outOfSyncApps.size()); for (Application app : outOfSyncApps) { try { String appName = app.getMetadata().getName(); log.info("Syncing out-of-sync application: {}", appName); syncService.syncApplication(appName); } catch (ArgoCDException e) { log.error("Failed to sync application: {}", app.getMetadata().getName(), e); } } } catch (ArgoCDException e) { log.error("Failed to get out-of-sync applications for scheduled sync", e); } } @Scheduled(cron = "${argocd.health-check.cron:0 */5 * * * *}") // Every 5 minutes public void checkApplicationHealth() { log.debug("Checking application health"); try { List<Application> unhealthyApps = appService.getUnhealthyApplications(); if (!unhealthyApps.isEmpty()) { log.warn("Found {} unhealthy applications: {}", unhealthyApps.size(), unhealthyApps.stream() .map(app -> app.getMetadata().getName()) .collect(Collectors.joining(", "))); // Could trigger alerts or auto-healing here } } catch (ArgoCDException e) { log.error("Failed to check application health", e); } } } Configuration
@Configuration @EnableScheduling @EnableConfigurationProperties(ArgoCDConfig.class) public class ArgoCDAutoConfiguration { @Bean @ConditionalOnMissingBean public ArgoCDClientFactory argoCDClientFactory(ArgoCDConfig config) { return new ArgoCDClientFactory(config); } @Bean @ConditionalOnMissingBean public ArgoCDRestClient argoCDRestClient(ArgoCDClientFactory factory) { return factory.createRestClient(); } @Bean @ConditionalOnMissingBean public ArgoCDKubernetesClient argoCDKubernetesClient(ArgoCDClientFactory factory) throws IOException { return factory.createKubernetesClient(); } @Bean @ConditionalOnMissingBean public ArgoCDSyncService argoCDSyncService(ArgoCDRestClient restClient, ArgoCDConfig config) { return new ArgoCDSyncService(restClient, config, new ObjectMapper()); } @Bean @ConditionalOnMissingBean public ArgoCDApplicationService argoCDApplicationService(ArgoCDRestClient restClient, ArgoCDKubernetesClient k8sClient) { return new ArgoCDApplicationService(restClient, k8sClient); } } Best Practices
- Error Handling: Always handle ArgoCD exceptions gracefully
- Retry Logic: Implement retry for transient failures
- Timeouts: Set appropriate timeouts for sync operations
- Security: Use secure authentication methods (tokens over basic auth)
- Monitoring: Track sync operations and application health
- Resource Cleanup: Close HTTP clients properly
- Idempotency: Design sync operations to be idempotent
This comprehensive ArgoCD Java integration provides a robust foundation for automating application deployments, managing sync operations, and monitoring application health in your GitOps workflows.