ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. This guide shows how to programmatically manage ArgoCD applications using Java.
Core Concepts
What is ArgoCD Application Sync?
- Synchronizing Kubernetes manifests from Git repositories to clusters
- Automated deployment and lifecycle management
- GitOps approach: Git as the single source of truth
- Programmatic control over ArgoCD applications
Key Operations:
- Create/Update Applications: Define what to deploy
- Sync Applications: Deploy latest changes
- Get Status: Monitor deployment health
- Rollback: Revert to previous versions
- Delete: Remove applications
Dependencies and Setup
Maven Dependencies
<properties> <kubernetes-client.version>6.7.2</kubernetes-client.version> <argo-cd.version>2.7.0</argo-cd.version> <spring-boot.version>3.1.0</spring-boot.version> <jackson.version>2.15.2</jackson.version> </properties> <dependencies> <!-- ArgoCD Client --> <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>17</classifier> </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-webflux</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>${jackson.version}</version> </dependency> </dependencies> ArgoCD Configuration
# application.yml argocd: server: url: ${ARGOCD_SERVER:https://argocd.example.com} token: ${ARGOCD_TOKEN:} username: ${ARGOCD_USERNAME:admin} password: ${ARGOCD_PASSWORD:} insecure: ${ARGOCD_INSECURE:false} timeout: 30000 defaults: namespace: argocd project: default sync-policy: automated: prune: true selfHeal: true Core Implementation
1. Configuration Classes
@Configuration @ConfigurationProperties(prefix = "argocd") @Data public class ArgoCDConfig { private Server server = new Server(); private Defaults defaults = new Defaults(); @Data public static class Server { private String url; private String token; private String username; private String password; private boolean insecure; private int timeout = 30000; } @Data public static class Defaults { private String namespace = "argocd"; private String project = "default"; private SyncPolicy syncPolicy = new SyncPolicy(); } @Data public static class SyncPolicy { private Automated automated = new Automated(); private Retry retry; private List<String> syncOptions; } @Data public static class Automated { private boolean prune = true; private boolean selfHeal = true; } @Data public static class Retry { private int limit = 5; private String backoff; } } 2. ArgoCD Client Service
@Component @Slf4j public class ArgoCDClient { private final ArgoCDConfig config; private final WebClient webClient; private final ObjectMapper objectMapper; public ArgoCDClient(ArgoCDConfig config, ObjectMapper objectMapper) { this.config = config; this.objectMapper = objectMapper; this.webClient = buildWebClient(); } private WebClient buildWebClient() { WebClient.Builder builder = WebClient.builder() .baseUrl(config.getServer().getUrl()) .defaultHeader("Content-Type", "application/json") .defaultHeader("Accept", "application/json"); // Add authentication if (config.getServer().getToken() != null) { builder.defaultHeader("Authorization", "Bearer " + config.getServer().getToken()); } else if (config.getServer().getUsername() != null) { // For basic auth (less common with tokens) String credentials = config.getServer().getUsername() + ":" + config.getServer().getPassword(); String base64Credentials = Base64.getEncoder().encodeToString(credentials.getBytes()); builder.defaultHeader("Authorization", "Basic " + base64Credentials); } return builder.build(); } public <T> T executeGet(String path, Class<T> responseType) { try { return webClient.get() .uri(path) .retrieve() .bodyToMono(responseType) .block(); } catch (WebClientResponseException e) { throw new ArgoCDException("GET request failed: " + path, e); } } public <T> T executePost(String path, Object body, Class<T> responseType) { try { return webClient.post() .uri(path) .bodyValue(body) .retrieve() .bodyToMono(responseType) .block(); } catch (WebClientResponseException e) { throw new ArgoCDException("POST request failed: " + path, e); } } public <T> T executePut(String path, Object body, Class<T> responseType) { try { return webClient.put() .uri(path) .bodyValue(body) .retrieve() .bodyToMono(responseType) .block(); } catch (WebClientResponseException e) { throw new ArgoCDException("PUT request failed: " + path, e); } } public void executeDelete(String path) { try { webClient.delete() .uri(path) .retrieve() .toBodilessEntity() .block(); } catch (WebClientResponseException e) { throw new ArgoCDException("DELETE request failed: " + path, e); } } } 3. Application Models
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class ArgoCDApplication { private V1ObjectMeta metadata; private ApplicationSpec spec; private ApplicationStatus status; @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class ApplicationSpec { private ApplicationDestination destination; private ApplicationSource source; private SyncPolicy syncPolicy; private String project; private List<Info> info; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class ApplicationDestination { private String server; private String namespace; private String name; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class ApplicationSource { private String repoURL; private String path; private String targetRevision; private String chart; private HelmSource helm; private KustomizeSource kustomize; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncPolicy { private Automated automated; private SyncPolicyRetry retry; private List<String> syncOptions; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Automated { private boolean prune; private boolean selfHeal; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncPolicyRetry { private Integer limit; private RetryBackoff backoff; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class RetryBackoff { private String duration; private String factor; private String maxDuration; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Info { private String name; private String value; } } @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ApplicationStatus { private List<ResourceStatus> resources; private List<Condition> conditions; private HealthStatus health; private SyncStatus sync; private String observedAt; private String sourceType; private String summary; @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class ResourceStatus { private String group; private String version; private String kind; private String namespace; private String name; private String status; private HealthStatus health; private Boolean hook; private Boolean requiresPruning; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class HealthStatus { private String status; private String message; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncStatus { private String status; private String revision; private List<ResourceStatus> resources; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Condition { private String type; private String message; private String lastTransitionTime; } } @Data @Builder @AllArgsConstructor @NoArgsConstructor public class SyncOperation { private String revision; private Boolean prune; private Boolean dryRun; private Boolean apply; private List<SyncStrategy> strategies; private List<SyncResource> resources; private List<String> syncOptions; private SyncOperationRetry retry; @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncStrategy { private String apply; private SyncStrategyHook hook; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncStrategyHook { private String force; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncResource { private String group; private String kind; private String name; private String namespace; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncOperationRetry { private Integer limit; private SyncOperationRetryBackoff backoff; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class SyncOperationRetryBackoff { private String duration; private Integer factor; private String maxDuration; } } 4. ArgoCD Application Service
@Service @Slf4j public class ArgoCDApplicationService { private final ArgoCDClient argoCDClient; private final ArgoCDConfig config; private final ObjectMapper objectMapper; private static final String APPLICATIONS_PATH = "/api/v1/applications"; private static final String SYNC_PATH = APPLICATIONS_PATH + "/{name}/sync"; private static final String ROLLBACK_PATH = APPLICATIONS_PATH + "/{name}/rollback"; public ArgoCDApplicationService(ArgoCDClient argoCDClient, ArgoCDConfig config, ObjectMapper objectMapper) { this.argoCDClient = argoCDClient; this.config = config; this.objectMapper = objectMapper; } // Create Application public ArgoCDApplication createApplication(CreateApplicationRequest request) { log.info("Creating ArgoCD application: {}", request.getName()); ArgoCDApplication application = buildApplication(request); return argoCDClient.executePost(APPLICATIONS_PATH, application, ArgoCDApplication.class); } // Get Application public ArgoCDApplication getApplication(String name) { log.debug("Getting ArgoCD application: {}", name); String path = APPLICATIONS_PATH + "/" + name; return argoCDClient.executeGet(path, ArgoCDApplication.class); } // List Applications public List<ArgoCDApplication> listApplications() { log.debug("Listing ArgoCD applications"); ApplicationList response = argoCDClient.executeGet(APPLICATIONS_PATH, ApplicationList.class); return response.getItems() != null ? response.getItems() : new ArrayList<>(); } // Sync Application public SyncResponse syncApplication(String applicationName, SyncOperation syncOperation) { log.info("Syncing ArgoCD application: {}", applicationName); String path = SYNC_PATH.replace("{name}", applicationName); return argoCDClient.executePost(path, syncOperation, SyncResponse.class); } // Sync Application with default options public SyncResponse syncApplication(String applicationName) { SyncOperation syncOperation = SyncOperation.builder() .prune(true) .dryRun(false) .apply(true) .syncOptions(List.of("CreateNamespace=true")) .build(); return syncApplication(applicationName, syncOperation); } // Wait for sync completion public boolean waitForSync(String applicationName, Duration timeout) { log.info("Waiting for application sync: {}", applicationName); long endTime = System.currentTimeMillis() + timeout.toMillis(); while (System.currentTimeMillis() < endTime) { try { ArgoCDApplication app = getApplication(applicationName); ApplicationStatus status = app.getStatus(); if (status != null && status.getSync() != null && "Synced".equals(status.getSync().getStatus()) && status.getHealth() != null && "Healthy".equals(status.getHealth().getStatus())) { log.info("Application {} synced successfully", applicationName); return true; } // Log sync progress if (status != null && status.getSync() != null) { log.debug("Sync status: {}, Health: {}", status.getSync().getStatus(), status.getHealth() != null ? status.getHealth().getStatus() : "Unknown"); } Thread.sleep(5000); // Wait 5 seconds before next check } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ArgoCDException("Wait for sync interrupted", e); } catch (Exception e) { log.warn("Error checking sync status: {}", e.getMessage()); } } log.error("Application {} sync timeout after {}", applicationName, timeout); return false; } // Update Application public ArgoCDApplication updateApplication(String name, UpdateApplicationRequest request) { log.info("Updating ArgoCD application: {}", name); String path = APPLICATIONS_PATH + "/" + name; // Get current application first ArgoCDApplication currentApp = getApplication(name); // Update spec with new values ArgoCDApplication.ApplicationSpec updatedSpec = updateApplicationSpec( currentApp.getSpec(), request); currentApp.setSpec(updatedSpec); return argoCDClient.executePut(path, currentApp, ArgoCDApplication.class); } // Delete Application public void deleteApplication(String name, boolean cascade) { log.info("Deleting ArgoCD application: {}", name); String path = APPLICATIONS_PATH + "/" + name; Map<String, Object> queryParams = new HashMap<>(); if (cascade) { queryParams.put("cascade", true); } // For query parameters, we'd need to modify the client argoCDClient.executeDelete(path); } // Get Application Resource Tree public ApplicationTree getApplicationTree(String name) { log.debug("Getting resource tree for application: {}", name); String path = APPLICATIONS_PATH + "/" + name + "/resource-tree"; return argoCDClient.executeGet(path, ApplicationTree.class); } // Rollback Application public SyncResponse rollbackApplication(String name, String revision) { log.info("Rolling back application {} to revision: {}", name, revision); String path = ROLLBACK_PATH.replace("{name}", name); Map<String, Object> rollbackRequest = new HashMap<>(); rollbackRequest.put("id", revision); rollbackRequest.put("prune", true); return argoCDClient.executePost(path, rollbackRequest, SyncResponse.class); } private ArgoCDApplication buildApplication(CreateApplicationRequest request) { V1ObjectMeta metadata = new V1ObjectMeta(); metadata.setName(request.getName()); metadata.setNamespace(config.getDefaults().getNamespace()); ArgoCDApplication.ApplicationDestination destination = ArgoCDApplication.ApplicationDestination.builder() .server(request.getDestinationServer()) .namespace(request.getDestinationNamespace()) .build(); ArgoCDApplication.ApplicationSource source = ArgoCDApplication.ApplicationSource.builder() .repoURL(request.getRepoUrl()) .path(request.getPath()) .targetRevision(request.getTargetRevision()) .build(); ArgoCDApplication.SyncPolicy syncPolicy = buildSyncPolicy(request); ArgoCDApplication.ApplicationSpec spec = ArgoCDApplication.ApplicationSpec.builder() .destination(destination) .source(source) .syncPolicy(syncPolicy) .project(config.getDefaults().getProject()) .build(); return ArgoCDApplication.builder() .metadata(metadata) .spec(spec) .build(); } private ArgoCDApplication.SyncPolicy buildSyncPolicy(CreateApplicationRequest request) { ArgoCDConfig.Automated configAutomated = config.getDefaults().getSyncPolicy().getAutomated(); ArgoCDApplication.Automated automated = ArgoCDApplication.Automated.builder() .prune(request.isPrune() != null ? request.isPrune() : configAutomated.isPrune()) .selfHeal(request.isSelfHeal() != null ? request.isSelfHeal() : configAutomated.isSelfHeal()) .build(); return ArgoCDApplication.SyncPolicy.builder() .automated(automated) .syncOptions(List.of("CreateNamespace=true")) .build(); } private ArgoCDApplication.ApplicationSpec updateApplicationSpec( ArgoCDApplication.ApplicationSpec currentSpec, UpdateApplicationRequest request) { ArgoCDApplication.ApplicationSource updatedSource = currentSpec.getSource(); if (request.getTargetRevision() != null) { updatedSource.setTargetRevision(request.getTargetRevision()); } if (request.getPath() != null) { updatedSource.setPath(request.getPath()); } return ArgoCDApplication.ApplicationSpec.builder() .destination(currentSpec.getDestination()) .source(updatedSource) .syncPolicy(currentSpec.getSyncPolicy()) .project(currentSpec.getProject()) .info(currentSpec.getInfo()) .build(); } } // Supporting DTOs @Data @Builder @AllArgsConstructor @NoArgsConstructor class CreateApplicationRequest { @NotBlank private String name; @NotBlank private String repoUrl; @NotBlank private String path; private String targetRevision; @NotBlank private String destinationServer; private String destinationNamespace; private Boolean prune; private Boolean selfHeal; private Map<String, String> labels; private Map<String, String> annotations; } @Data @Builder @AllArgsConstructor @NoArgsConstructor class UpdateApplicationRequest { private String targetRevision; private String path; private Boolean prune; private Boolean selfHeal; } @Data class ApplicationList { private List<ArgoCDApplication> items; } @Data class SyncResponse { private List<OperationState> operationState; @Data public static class OperationState { private String phase; private String message; private String startedAt; private String finishedAt; private SyncOperationResult syncResult; } @Data public static class SyncOperationResult { private List<ResourceResult> resources; private String revision; } @Data public static class ResourceResult { private String group; private String version; private String kind; private String namespace; private String name; private String status; private String message; private String hookType; private String hookPhase; private String syncPhase; } } @Data class ApplicationTree { private List<ResourceNode> nodes; @Data public static class ResourceNode { private String uid; private String parentRefs; private String resourceVersion; private String name; private String namespace; private String kind; private String group; private HealthStatus health; private List<InfoItem> info; private List<ResourceRef> networkingInfo; } @Data public static class HealthStatus { private String status; private String message; } @Data public static class InfoItem { private String name; private String value; } @Data public static class ResourceRef { private String targetRef; private String external; } } // Custom Exceptions public class ArgoCDException extends RuntimeException { public ArgoCDException(String message) { super(message); } public ArgoCDException(String message, Throwable cause) { super(message, cause); } } 5. Application Synchronization Manager
@Service @Slf4j public class ApplicationSyncManager { private final ArgoCDApplicationService applicationService; private final SlackAlertService slackAlertService; // From previous example public ApplicationSyncManager(ArgoCDApplicationService applicationService, SlackAlertService slackAlertService) { this.applicationService = applicationService; this.slackAlertService = slackAlertService; } public SyncResult syncApplicationWithRetry(String applicationName, int maxRetries) { log.info("Starting sync with retry for application: {}", applicationName); for (int attempt = 1; attempt <= maxRetries; attempt++) { try { log.info("Sync attempt {}/{} for {}", attempt, maxRetries, applicationName); // Trigger sync SyncResponse syncResponse = applicationService.syncApplication(applicationName); // Wait for sync completion boolean syncSuccess = applicationService.waitForSync(applicationName, Duration.ofMinutes(10)); if (syncSuccess) { log.info("Application {} synced successfully on attempt {}", applicationName, attempt); sendSyncSuccessAlert(applicationName, attempt); return SyncResult.success(applicationName, attempt); } else { log.warn("Application {} sync failed on attempt {}", applicationName, attempt); if (attempt < maxRetries) { // Wait before retry Thread.sleep(Duration.ofSeconds(30).toMillis()); } } } catch (Exception e) { log.error("Sync attempt {} failed for {}: {}", attempt, applicationName, e.getMessage()); if (attempt == maxRetries) { sendSyncFailureAlert(applicationName, e.getMessage()); return SyncResult.failure(applicationName, e.getMessage()); } try { Thread.sleep(Duration.ofSeconds(30).toMillis()); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new ArgoCDException("Sync interrupted", ie); } } } return SyncResult.failure(applicationName, "Max retries exceeded"); } public DeploymentResult deployApplication(DeploymentRequest request) { log.info("Starting deployment for application: {}", request.getApplicationName()); try { // Check if application exists ArgoCDApplication existingApp = null; try { existingApp = applicationService.getApplication(request.getApplicationName()); } catch (ArgoCDException e) { log.debug("Application does not exist, will create: {}", request.getApplicationName()); } if (existingApp == null) { // Create new application CreateApplicationRequest createRequest = CreateApplicationRequest.builder() .name(request.getApplicationName()) .repoUrl(request.getRepoUrl()) .path(request.getPath()) .targetRevision(request.getTargetRevision()) .destinationServer(request.getCluster()) .destinationNamespace(request.getNamespace()) .prune(true) .selfHeal(true) .build(); applicationService.createApplication(createRequest); log.info("Created new application: {}", request.getApplicationName()); } else { // Update existing application UpdateApplicationRequest updateRequest = UpdateApplicationRequest.builder() .targetRevision(request.getTargetRevision()) .path(request.getPath()) .build(); applicationService.updateApplication(request.getApplicationName(), updateRequest); log.info("Updated existing application: {}", request.getApplicationName()); } // Sync the application SyncResult syncResult = syncApplicationWithRetry(request.getApplicationName(), 3); return DeploymentResult.builder() .applicationName(request.getApplicationName()) .success(syncResult.isSuccess()) .message(syncResult.getMessage()) .timestamp(Instant.now()) .build(); } catch (Exception e) { log.error("Deployment failed for {}: {}", request.getApplicationName(), e.getMessage()); sendDeploymentFailureAlert(request.getApplicationName(), e.getMessage()); return DeploymentResult.builder() .applicationName(request.getApplicationName()) .success(false) .message("Deployment failed: " + e.getMessage()) .timestamp(Instant.now()) .build(); } } public RollbackResult rollbackApplication(String applicationName, String targetRevision) { log.info("Rolling back application {} to revision: {}", applicationName, targetRevision); try { SyncResponse rollbackResponse = applicationService.rollbackApplication( applicationName, targetRevision); // Wait for rollback to complete boolean rollbackSuccess = applicationService.waitForSync(applicationName, Duration.ofMinutes(5)); RollbackResult result = RollbackResult.builder() .applicationName(applicationName) .targetRevision(targetRevision) .success(rollbackSuccess) .timestamp(Instant.now()) .build(); if (rollbackSuccess) { sendRollbackSuccessAlert(applicationName, targetRevision); } else { sendRollbackFailureAlert(applicationName, targetRevision); } return result; } catch (Exception e) { log.error("Rollback failed for {}: {}", applicationName, e.getMessage()); sendRollbackFailureAlert(applicationName, targetRevision); return RollbackResult.builder() .applicationName(applicationName) .targetRevision(targetRevision) .success(false) .message("Rollback failed: " + e.getMessage()) .timestamp(Instant.now()) .build(); } } public ApplicationHealthStatus getApplicationHealth(String applicationName) { try { ArgoCDApplication application = applicationService.getApplication(applicationName); ApplicationStatus status = application.getStatus(); if (status == null) { return ApplicationHealthStatus.unknown(applicationName, "No status available"); } String syncStatus = status.getSync() != null ? status.getSync().getStatus() : "Unknown"; String healthStatus = status.getHealth() != null ? status.getHealth().getStatus() : "Unknown"; return ApplicationHealthStatus.builder() .applicationName(applicationName) .syncStatus(syncStatus) .healthStatus(healthStatus) .syncMessage(status.getSync() != null ? status.getSync().getRevision() : "") .healthMessage(status.getHealth() != null ? status.getHealth().getMessage() : "") .observedAt(status.getObservedAt()) .build(); } catch (Exception e) { log.error("Failed to get health status for {}: {}", applicationName, e.getMessage()); return ApplicationHealthStatus.unknown(applicationName, e.getMessage()); } } private void sendSyncSuccessAlert(String applicationName, int attempt) { slackAlertService.sendSimpleMessage( String.format(":white_check_mark: Application `%s` synced successfully (attempt %d)", applicationName, attempt)); } private void sendSyncFailureAlert(String applicationName, String error) { slackAlertService.sendRichAlert(AlertRequest.builder() .level(AlertRequest.AlertLevel.ERROR) .title("Sync Failed") .message(String.format("Application `%s` sync failed", applicationName)) .details(Map.of("error", error)) .build()); } private void sendDeploymentFailureAlert(String applicationName, String error) { slackAlertService.sendRichAlert(AlertRequest.builder() .level(AlertRequest.AlertLevel.ERROR) .title("Deployment Failed") .message(String.format("Deployment of `%s` failed", applicationName)) .details(Map.of("error", error)) .build()); } private void sendRollbackSuccessAlert(String applicationName, String revision) { slackAlertService.sendSimpleMessage( String.format(":rewind: Application `%s` rolled back to revision `%s`", applicationName, revision)); } private void sendRollbackFailureAlert(String applicationName, String revision) { slackAlertService.sendRichAlert(AlertRequest.builder() .level(AlertRequest.AlertLevel.ERROR) .title("Rollback Failed") .message(String.format("Rollback of `%s` to `%s` failed", applicationName, revision)) .build()); } } // Result DTOs @Data @Builder @AllArgsConstructor @NoArgsConstructor class SyncResult { private String applicationName; private boolean success; private String message; private int attempts; private Instant timestamp; public static SyncResult success(String applicationName, int attempts) { return SyncResult.builder() .applicationName(applicationName) .success(true) .message("Sync completed successfully") .attempts(attempts) .timestamp(Instant.now()) .build(); } public static SyncResult failure(String applicationName, String error) { return SyncResult.builder() .applicationName(applicationName) .success(false) .message(error) .timestamp(Instant.now()) .build(); } } @Data @Builder @AllArgsConstructor @NoArgsConstructor class DeploymentResult { private String applicationName; private boolean success; private String message; private Instant timestamp; } @Data @Builder @AllArgsConstructor @NoArgsConstructor class RollbackResult { private String applicationName; private String targetRevision; private boolean success; private String message; private Instant timestamp; } @Data @Builder @AllArgsConstructor @NoArgsConstructor class ApplicationHealthStatus { private String applicationName; private String syncStatus; private String healthStatus; private String syncMessage; private String healthMessage; private String observedAt; private Instant checkedAt; public static ApplicationHealthStatus unknown(String applicationName, String message) { return ApplicationHealthStatus.builder() .applicationName(applicationName) .syncStatus("Unknown") .healthStatus("Unknown") .healthMessage(message) .checkedAt(Instant.now()) .build(); } public boolean isHealthy() { return "Synced".equals(syncStatus) && "Healthy".equals(healthStatus); } } @Data @Builder @AllArgsConstructor @NoArgsConstructor class DeploymentRequest { private String applicationName; private String repoUrl; private String path; private String targetRevision; private String cluster; private String namespace; private Map<String, String> parameters; } 6. REST Controller
@RestController @RequestMapping("/api/argocd") @Slf4j public class ArgoCDController { private final ApplicationSyncManager syncManager; private final ArgoCDApplicationService applicationService; public ArgoCDController(ApplicationSyncManager syncManager, ArgoCDApplicationService applicationService) { this.syncManager = syncManager; this.applicationService = applicationService; } @PostMapping("/applications") public ResponseEntity<DeploymentResult> createApplication(@Valid @RequestBody DeploymentRequest request) { log.info("Received deployment request for: {}", request.getApplicationName()); DeploymentResult result = syncManager.deployApplication(request); if (result.isSuccess()) { return ResponseEntity.ok(result); } else { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @PostMapping("/applications/{name}/sync") public ResponseEntity<SyncResult> syncApplication(@PathVariable String name) { log.info("Received sync request for application: {}", name); SyncResult result = syncManager.syncApplicationWithRetry(name, 3); if (result.isSuccess()) { return ResponseEntity.ok(result); } else { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @PostMapping("/applications/{name}/rollback") public ResponseEntity<RollbackResult> rollbackApplication( @PathVariable String name, @RequestParam String revision) { log.info("Received rollback request for application: {} to revision: {}", name, revision); RollbackResult result = syncManager.rollbackApplication(name, revision); if (result.isSuccess()) { return ResponseEntity.ok(result); } else { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } @GetMapping("/applications/{name}/health") public ResponseEntity<ApplicationHealthStatus> getApplicationHealth(@PathVariable String name) { ApplicationHealthStatus health = syncManager.getApplicationHealth(name); return ResponseEntity.ok(health); } @GetMapping("/applications") public ResponseEntity<List<ArgoCDApplication>> listApplications() { List<ArgoCDApplication> applications = applicationService.listApplications(); return ResponseEntity.ok(applications); } @GetMapping("/applications/{name}") public ResponseEntity<ArgoCDApplication> getApplication(@PathVariable String name) { ArgoCDApplication application = applicationService.getApplication(name); return ResponseEntity.ok(application); } @DeleteMapping("/applications/{name}") public ResponseEntity<Map<String, String>> deleteApplication( @PathVariable String name, @RequestParam(defaultValue = "true") boolean cascade) { applicationService.deleteApplication(name, cascade); return ResponseEntity.ok(Map.of( "status", "success", "message", "Application " + name + " deleted" )); } } 7. Scheduled Sync Monitor
@Component @Slf4j public class ScheduledSyncMonitor { private final ApplicationSyncManager syncManager; private final ArgoCDApplicationService applicationService; private final SlackAlertService slackAlertService; private final Set<String> monitoredApplications; public ScheduledSyncMonitor(ApplicationSyncManager syncManager, ArgoCDApplicationService applicationService, SlackAlertService slackAlertService) { this.syncManager = syncManager; this.applicationService = applicationService; this.slackAlertService = slackAlertService; this.monitoredApplications = ConcurrentHashMap.newKeySet(); } @Scheduled(fixedRate = 300000) // 5 minutes public void monitorApplicationsHealth() { if (monitoredApplications.isEmpty()) { return; } log.debug("Monitoring health of {} applications", monitoredApplications.size()); for (String appName : monitoredApplications) { try { ApplicationHealthStatus health = syncManager.getApplicationHealth(appName); if (!health.isHealthy()) { log.warn("Application {} is unhealthy: Sync={}, Health={}", appName, health.getSyncStatus(), health.getHealthStatus()); sendHealthAlert(appName, health); } } catch (Exception e) { log.error("Failed to monitor application {}: {}", appName, e.getMessage()); } } } @Scheduled(cron = "0 0 6 * * ?") // Daily at 6 AM public void performDailySync() { log.info("Starting daily sync of monitored applications"); for (String appName : monitoredApplications) { try { SyncResult result = syncManager.syncApplicationWithRetry(appName, 2); if (!result.isSuccess()) { log.error("Daily sync failed for {}: {}", appName, result.getMessage()); } } catch (Exception e) { log.error("Daily sync failed for {}: {}", appName, e.getMessage()); } } } public void addMonitoredApplication(String applicationName) { monitoredApplications.add(applicationName); log.info("Added application to monitoring: {}", applicationName); } public void removeMonitoredApplication(String applicationName) { monitoredApplications.remove(applicationName); log.info("Removed application from monitoring: {}", applicationName); } public Set<String> getMonitoredApplications() { return Collections.unmodifiableSet(monitoredApplications); } private void sendHealthAlert(String appName, ApplicationHealthStatus health) { slackAlertService.sendRichAlert(AlertRequest.builder() .level(AlertRequest.AlertLevel.WARNING) .title("Application Health Alert") .message(String.format("Application `%s` is unhealthy", appName)) .details(Map.of( "syncStatus", health.getSyncStatus(), "healthStatus", health.getHealthStatus(), "healthMessage", health.getHealthMessage(), "observedAt", health.getObservedAt() )) .build()); } } Configuration
# application.yml spring: application: name: argocd-sync-service argocd: server: url: ${ARGOCD_SERVER:https://argocd.example.com} token: ${ARGOCD_TOKEN:} insecure: false timeout: 30000 defaults: namespace: argocd project: default sync-policy: automated: prune: true selfHeal: true management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always logging: level: com.example.argocd: DEBUG Best Practices
- Error Handling: Implement robust error handling and retry mechanisms
- Security: Use secure token authentication and avoid storing credentials
- Monitoring: Track sync operations and application health
- Idempotency: Ensure operations can be safely retried
- Resource Cleanup: Properly manage resources and connections
- Alerting: Integrate with alerting systems for critical failures
@Component public class ArgoCDHealthCheck implements HealthIndicator { private final ArgoCDApplicationService applicationService; public ArgoCDHealthCheck(ArgoCDApplicationService applicationService) { this.applicationService = applicationService; } @Override public Health health() { try { // Test connection by listing applications applicationService.listApplications(); return Health.up().withDetail("server", "reachable").build(); } catch (Exception e) { return Health.down() .withDetail("server", "unreachable") .withDetail("error", e.getMessage()) .build(); } } } Conclusion
ArgoCD Application Sync in Java provides:
- Programmatic control over Kubernetes deployments
- GitOps automation with full lifecycle management
- Robust error handling with retry mechanisms
- Comprehensive monitoring and alerting
- RESTful API for integration with other systems
This implementation enables teams to automate their deployment pipelines, ensure consistent application states, and quickly respond to deployment issues through programmatic control of ArgoCD.