Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
55f1480
Initial commit `v7.0.0`
hkarthik7 Dec 1, 2025
6da4ad9
Updated README.md
hkarthik7 Dec 1, 2025
26f3932
Added tests for SPN authentication
hkarthik7 Dec 5, 2025
73e9a2d
Fix for issue #96
hkarthik7 Dec 5, 2025
890bc4a
Updated tests
hkarthik7 Dec 5, 2025
0c6fddf
Removed legacy code
hkarthik7 Dec 5, 2025
2b3b2b5
Minor tweak
hkarthik7 Dec 5, 2025
03f66ba
Minor tweak
hkarthik7 Dec 5, 2025
62515ac
Fix for issue #96
hkarthik7 Dec 5, 2025
ef958c7
Moved to default package
hkarthik7 Dec 5, 2025
16e9a1a
Added SPN authentication
hkarthik7 Dec 5, 2025
3145c09
Changes to packages
hkarthik7 Jan 8, 2026
4ab8f9a
Updated CHANGELOG.md
hkarthik7 Jan 8, 2026
115db3c
Updated CHANGELOG.md
hkarthik7 Jan 8, 2026
0f07ca8
Bumped version to `v7.0.0`
hkarthik7 Jan 8, 2026
2b44b6f
Updated README
hkarthik7 Jan 8, 2026
c924f63
Minor tweaks
hkarthik7 Jan 8, 2026
4311de1
Updated README
hkarthik7 Jan 8, 2026
afa0168
Updated accounts test
hkarthik7 Feb 26, 2026
4cb4e90
Added new Api exception types
hkarthik7 Feb 26, 2026
e19d1b8
Updated tests
hkarthik7 Feb 26, 2026
12a874e
Added `CommentsApi`
hkarthik7 Feb 26, 2026
2e2eb2c
Added `CommentsApi`
hkarthik7 Feb 26, 2026
09a9c70
Added deserialize method
hkarthik7 Feb 26, 2026
933e2c2
Minor update
hkarthik7 Feb 26, 2026
bbd86e1
Response handler pipeline initial commit
hkarthik7 Feb 26, 2026
cb0e466
Added methods for DI
hkarthik7 Feb 26, 2026
6209a12
Added Object deserialize method
hkarthik7 Feb 26, 2026
9c9f5b4
Minor update
hkarthik7 Feb 26, 2026
fdf9cd4
Minor update
hkarthik7 Feb 26, 2026
d33b5c6
Fix: Issue #101
hkarthik7 Feb 26, 2026
9f18f11
Code clean up to accommodate pipeline architecture
hkarthik7 Feb 26, 2026
c29605c
Added validation
hkarthik7 Feb 26, 2026
f21191a
Added `CommentsApi` version
hkarthik7 Feb 26, 2026
5069d30
Minor changes
hkarthik7 Feb 26, 2026
a307538
`CommentsApi` initial commit
hkarthik7 Feb 26, 2026
1aad48a
Updated `CommentsApi` with new methods
hkarthik7 Feb 27, 2026
aa35246
Updated tests
hkarthik7 Feb 27, 2026
225f260
Updated CHANGELOG.md
hkarthik7 Feb 27, 2026
d6ff99f
Moved to `artifacts` package
hkarthik7 Feb 27, 2026
a03a9fa
Moved to `artifacts` package
hkarthik7 Feb 27, 2026
745d2bd
Optimized imports
hkarthik7 Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

# 7.0.0

**Major release**
**Breaking changes**

- Fixed issues:
- Issue: [GitChange.changeType: InvalidArgumentValueException: The body of the request contains invalid Json. #96](https://github.com/hkarthik7/azure-devops-java-sdk/issues/96)
- Issue: [is there a way to add comments? #103](https://github.com/hkarthik7/azure-devops-java-sdk/issues/103)
- Issue: [client attempts to always deserlilize the response regardless if there's an error #102](https://github.com/hkarthik7/azure-devops-java-sdk/issues/102)
- Issue: [LookupService keeps the first credentails of the first succesffull call and ignores credentials from subsequent calls #101](https://github.com/hkarthik7/azure-devops-java-sdk/issues/101)
- Removed support for legacy API
- Changes in types packages
- Added support for authentication via service principal client id and secret
- Added `Comments Api`

# 6.1.3

- Added support for Pull Request Iterations in **GitApi**.
Expand Down
37 changes: 33 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# azure-devops-java-sdk

# :loudspeaker: Legacy Api is decommissioned in v7.0.0. :exclamation::heavy_exclamation_mark:

[![Build Status](https://dev.azure.com/harishkarthic/azure-devops-java-sdk/_apis/build/status/hkarthik7.azure-devops-java-sdk?branchName=main)](https://dev.azure.com/harishkarthic/azure-devops-java-sdk/_build/latest?definitionId=8&branchName=main)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/hkarthik7/azure-devops-java-sdk/blob/main/LICENSE)
[![Documentation Status](https://readthedocs.org/projects/azure-devops-java-sdk-docs/badge/?version=latest)](https://azure-devops-java-sdk-docs.readthedocs.io/en/latest/?badge=latest)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.hkarthik7/azd.svg)](https://search.maven.org/artifact/io.github.hkarthik7/azd/6.1.3/jar)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.hkarthik7/azd.svg)](https://search.maven.org/artifact/io.github.hkarthik7/azd/7.0.0/jar)

**azd** library provides a convenient way to manage and interact with **Azure DevOps Services** REST API with ease. This SDK offers a set of APIs and utilities
with declarative syntax and provide functionalities to the significant services.
Expand Down Expand Up @@ -33,7 +35,7 @@ To download the library and use it in your project, just add below in your pom.x
<dependency>
<groupId>io.github.hkarthik7</groupId>
<artifactId>azd</artifactId>
<version>6.1.3</version>
<version>7.0.0</version>
</dependency>
```

Expand All @@ -43,7 +45,7 @@ To download the library and use it in your project, just add below in your pom.x
<dependency>
<groupId>io.github.hkarthik7</groupId>
<artifactId>azd</artifactId>
<version>6.1.3</version>
<version>7.0.0</version>
<classifier>javadoc</classifier>
</dependency>
```
Expand All @@ -54,7 +56,7 @@ To download the library and use it in your project, just add below in your pom.x
<dependency>
<groupId>io.github.hkarthik7</groupId>
<artifactId>azd</artifactId>
<version>6.1.3</version>
<version>7.0.0</version>
<classifier>sources</classifier>
</dependency>
```
Expand Down Expand Up @@ -100,6 +102,31 @@ public class Main {
}
```

**Authentication using service principal**

To use spn authentication you should create a new app registration in Entra ID, grant Api permissions on Azure DevOps, select right scope
and grant admin consent.

```java
public class Main {
public static void main(String[] args) {
String project = "myProject";
String baseUrl = "https://dev.azure.com/{organization}";
// or TFS URL
String baseUrl = "https://{server:port}/tfs/{collection}";

String tenantId = "tenantId";
String clientId = "clientId";
String clientSecret = "clientSecret";

AccessTokenCredential spn = new ServicePrincipalAccessTokenCredential(
baseUrl,
project, tenantId,
clientId, clientSecret);
}
}
```

- Sample usage

```java
Expand All @@ -110,6 +137,8 @@ public class Main {
AzDServiceClient client = AzDService.builder().authentication(pat).buildClient();
// or
AzDServiceClient client = AzDService.builder().authentication(oauth).buildClient();
// or
AzDServiceClient client = AzDService.builder().authentication(spn).buildClient();

try {
// Get the list of projects. This return a future object.
Expand Down
2 changes: 1 addition & 1 deletion azd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.hkarthik7</groupId>
<artifactId>azd</artifactId>
<version>6.1.3</version>
<version>7.0.0</version>
<packaging>jar</packaging>

<name>azd</name>
Expand Down
19 changes: 0 additions & 19 deletions azd/src/main/java/org/azd/abstractions/ClientConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package org.azd.abstractions;

import org.azd.abstractions.handlers.RetryHandler;

/**
* Singleton class that configures Api request options.
*/
public class ClientConfiguration {
private final static ClientConfiguration instance = new ClientConfiguration();
private static RequestOption reqOption;
private static RetryHandler retry;

private ClientConfiguration() {
}
Expand All @@ -29,27 +26,11 @@ public RequestOption getRequestOption() {
return reqOption;
}

/**
* Get the retry handler object.
* @return RetryHandler.
*/
public RetryHandler getRetryHandler() {
return retry;
}

/**
* Configures the request option.
* @param requestOption Request option object to configure.
*/
public void configureRequestOption(RequestOption requestOption) {
reqOption = requestOption;
}

/**
* Configures the retry handler options.
* @param retryHandler Retry handler object to configure.
*/
public void configureRetryHandler(RetryHandler retryHandler) {
retry = retryHandler;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.azd.common.Constants;
import org.azd.exceptions.AzDException;

import java.net.URI;
import java.net.http.HttpRequest;

/**
Expand Down
28 changes: 16 additions & 12 deletions azd/src/main/java/org/azd/abstractions/InstanceFactory.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.azd.abstractions;

import org.azd.abstractions.handlers.RequestExecutor;
import org.azd.abstractions.handlers.RetryHandler;
import org.azd.abstractions.handlers.*;
import org.azd.abstractions.pipelines.ResponsePipeline;
import org.azd.abstractions.pipelines.ResponsePipelineBuilder;
import org.azd.abstractions.serializer.JsonSerializer;
import org.azd.abstractions.serializer.SerializerContext;
import org.azd.authentication.AccessTokenCredential;
Expand All @@ -24,6 +25,10 @@ public static SerializerContext createSerializerContext() {
return new JsonSerializer();
}

public static RetryHandler createRetryHandler() {
return new DefaultRetryHandler();
}

/**
* Creates an instance of HttpClient.
* @return HttpClient {@link HttpClient}.
Expand All @@ -46,16 +51,15 @@ public static HttpRequest createHttpRequest(AccessTokenCredential accessTokenCre
}

/**
* Creates an instance of response handler object.
* @param accessTokenCredential Access token credential object.
* @param requestInformation Request information object to set the request url, request body.
* @return ResponseHandler {@link ResponseHandler}.
* Creates the response pipeline with all handlers.
* @return ResponsePipeline with default response handlers.
*/
public static ResponseHandler createResponseHandler(AccessTokenCredential accessTokenCredential,
RequestInformation requestInformation) {
var retryHandler = ClientConfiguration.getInstance().getRetryHandler();
retryHandler = retryHandler == null ? new RetryHandler(
new RequestExecutor(accessTokenCredential, requestInformation)) : retryHandler;
return ResponseHandler.create(retryHandler);
public static ResponsePipeline createResponsePipeline() {
return ResponsePipelineBuilder.create()
.add(ApiResponseHandler::new)
.add(RedirectResponseHandler::new)
.add(() -> new ErrorResponseHandler(createSerializerContext()))
.add(() -> new SerializerHandler(createSerializerContext()))
.build();
}
}
47 changes: 30 additions & 17 deletions azd/src/main/java/org/azd/abstractions/ResponseHandler.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
package org.azd.abstractions;

import org.azd.abstractions.handlers.DefaultResponseHandler;
import org.azd.abstractions.handlers.RetryHandler;
import org.azd.abstractions.handlers.ResponseContext;

import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

/**
* Handler the Api response.
* Handles the Api response.
*/
public abstract class ResponseHandler {
protected static ApiResponse apiResponse;
protected final RetryHandler retryHandler;
protected ResponseHandler next;
private static ApiResponse apiResponse;

protected ResponseHandler(RetryHandler retryHandler) {
this.retryHandler = retryHandler;
/**
* Sets the next handler in pipeline.
* @param next Next handler to invoke
* @return ResponseHandler handler.
*/
public ResponseHandler setNext(ResponseHandler next) {
this.next = next;
return next;
}

/**
* Creates an instance of Response handler.
* @param retryHandler Retry handler to retry the request.
* @return Response handler object.
* Invokes the next handler in the pipeline
* @param context Response context object container.
* @return Result of invocation.
*/
public static ResponseHandler create(RetryHandler retryHandler) {
return new DefaultResponseHandler(retryHandler);
protected CompletableFuture<Void> nextAsync(ResponseContext context) {
if (next == null) {
return CompletableFuture.completedFuture(null);
}
return next.handleAsync(context);
}

/**
Expand All @@ -35,11 +43,16 @@ public static ApiResponse getResponse() {

/**
* Handles the Api response.
* @param response Http response object.
* @param requestInformation Request information object. {@link RequestInformation}.
* @param context ResponseContext object {@link ResponseContext}
* @return Java type value that is passed.
* @param <T> Type parameter.
*/
public abstract <T> T handle(HttpResponse<T> response, RequestInformation requestInformation);
public abstract CompletableFuture<Void> handleAsync(ResponseContext context);

/**
* Sets the Api response object.
* @param response ApiResponse object to set {@link ApiResponse}
*/
public static void setResponse(ApiResponse response) {
apiResponse = response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.azd.abstractions.handlers;

import org.azd.abstractions.ApiResponse;
import org.azd.abstractions.ResponseHandler;
import org.azd.enums.HttpStatusCode;

import java.util.concurrent.CompletableFuture;

public final class ApiResponseHandler extends ResponseHandler {
@Override
public CompletableFuture<Void> handleAsync(ResponseContext context) {
var response = context.response();

setResponse(new ApiResponse(
HttpStatusCode.from(response.statusCode()),
response.headers().map(),
response.body(),
response.request().uri().toString(),
context.request()
));

return nextAsync(context);
}
}
36 changes: 36 additions & 0 deletions azd/src/main/java/org/azd/abstractions/handlers/ContentType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.azd.abstractions.handlers;

import org.azd.enums.CustomHeader;
import org.azd.utils.StringUtils;

import java.net.http.HttpResponse;
import java.util.Locale;
import java.util.Objects;

public final class ContentType {
private final String value;

private ContentType(String value) {
this.value = value.toLowerCase(Locale.ROOT);
}

public static ContentType from(HttpResponse<?> response) {
Objects.requireNonNull(response, "Response cannot be null.");

return response.headers()
.firstValue(CustomHeader.JSON_CONTENT_TYPE.getName())
.map(x -> new ContentType(x.split(";")[0].trim()))
.orElse(new ContentType(StringUtils.EMPTY));

}

boolean isJson() { return value.equals(CustomHeader.JSON_CONTENT_TYPE.getValue()); }
boolean isText() { return value.equals(CustomHeader.TEXT_CONTENT.getValue()); }
boolean isHtml() { return value.equals(CustomHeader.HTML_CONTENT.getValue()); }
boolean isXml() { return value.equals(CustomHeader.XML_CONTENT_TYPE.getValue()); }

@Override
public String toString() {
return value;
}
}

This file was deleted.

Loading