Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a68cb29
test (MultiPart): A multi part boundary can contains '--' sequence. R…
JPPortier Jul 21, 2026
b036e86
Merge pull request #32 from sinch/jpportier/test-unit-tests-failure
JPPortier Jul 22, 2026
b7fb156
CI: Support matrix compilation [21,25] for examples
JPPortier Jul 22, 2026
6c6cb68
CI: restrict dependency review to public repo only
JPPortier Jul 22, 2026
1af2240
Potential fix for pull request finding
JPPortier Jul 22, 2026
5cf306c
CI: restrict dependency review to public repo only
JPPortier Jul 23, 2026
e682176
CI: Support matrix compilation [21,25] for examples
JPPortier Jul 23, 2026
4ec1ca8
Sprint 11 OAS synch changes (#35)
edu-sinch Aug 3, 2026
f06c030
DEVEXP-1542: Retry-after response header on HTTP429 for OAuthManager …
edu-sinch Aug 11, 2026
e8ca8ea
Addressed comments
edu-sinch Aug 12, 2026
cf989cb
Merge branch 'main' into feat/V2.2-mergeMain
edu-sinch Aug 14, 2026
8abe3f3
Removed empty empty E2E tests
edu-sinch Aug 14, 2026
a6d25e7
Merge pull request #43 from sinch/feat/V2.2-mergeMain
edu-sinch Aug 17, 2026
cc89224
Comments addressed part 2
edu-sinch Aug 17, 2026
4502bd3
Merge pull request #37 from sinch/edu-sinch/DEVEXP-1542-RetryAfterOnt…
edu-sinch Aug 18, 2026
1975c2d
DEVEXP-1581: Sprint 13 OAS Syncho, sources generated
edu-sinch Aug 19, 2026
69ef055
Merge pull request #45 from sinch/edu-sinch/DEVEXP-1581-Sprint13OASSy…
edu-sinch Aug 19, 2026
421e9a7
DEVEXP-1592: Run E2E tests against the remote mock server, temporary …
edu-sinch Aug 21, 2026
ba9fc14
Merge pull request #48 from sinch/edu-sinch/DEVEXP-1592-UpdateCIConfi…
edu-sinch Aug 21, 2026
2f78172
DEVEXP-1484: Support HTTP-429 retry policy for all endpoints (#44)
edu-sinch Aug 25, 2026
d4441c7
Merge branch 'main' into feat/V2.2-next
JPPortier Aug 25, 2026
67d6e77
DEVEXP-1652: Support DICE in customCallout (Voice V1), sources generated
edu-sinch Sep 17, 2026
caabc28
Merge pull request #51 from sinch/edu-sinch/DEVEXP-1652-VoiceV1AddSup…
edu-sinch Sep 17, 2026
f337648
fix(deps): bump httpclient5 5.6.2 -> 5.6.3 (CVE-2026-64607)
edu-sinch Sep 17, 2026
790c142
CHANGELOG uodated: add Voice dice entry
edu-sinch Sep 17, 2026
d367f7c
Merge pull request #52 from sinch/fix/httpclient5-5.6.3-CVE-2026-64607
edu-sinch Sep 17, 2026
181bbd1
Fixed path for verification e2e tests
edu-sinch Sep 17, 2026
4d1b5ba
build (release): Bump version to 2.2.0 for sources
invalid-email-address Sep 17, 2026
e308af8
[release] Set release & tag: 2.2.0
invalid-email-address Sep 17, 2026
add010c
[release] Set next version: 2.3.0-SNAPSHOT
invalid-email-address Sep 17, 2026
1c6dd01
build (release): Set next version to 2.3.0-dev for sources
invalid-email-address Sep 17, 2026
17f9091
CHANGELOG updated: set v2.2.0 release date
edu-sinch Sep 17, 2026
36c5345
Merge branch 'main' into release-2.2.0
edu-sinch Sep 17, 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
35 changes: 16 additions & 19 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,24 @@ jobs:
name: Build Java ${{ matrix.java-version }}

runs-on: ubuntu-latest
timeout-minutes: 10

strategy:
matrix:
java-version: ['8', '11', '17', '21', '25']
# DEVEXP-1592 (temporary workaround): only Java 21 is built for now.
# TODO: restore ['8', '11', '17', '21', '25'] along with the commented 'include' entries below.
java-version: ['21']
include:
- java-version: '8'
maven-args: '-Dmaven.test.skip=true -Dspotless.check.skip=true clean package'
- java-version: '11'
maven-args: '-Dspotless.check.skip=true clean package javadoc:javadoc'
- java-version: '17'
maven-args: 'clean package javadoc:javadoc'
# - java-version: '8'
# maven-args: '-Dmaven.test.skip=true -Dspotless.check.skip=true clean package'
# - java-version: '11'
# maven-args: '-Dspotless.check.skip=true clean package javadoc:javadoc'
# - java-version: '17'
# maven-args: 'clean package javadoc:javadoc'
- java-version: '21'
maven-args: 'clean package javadoc:javadoc'
- java-version: '25'
maven-args: 'clean package javadoc:javadoc'
# - java-version: '25'
# maven-args: 'clean package javadoc:javadoc'
steps:
- uses: actions/checkout@v3
- name: Set up JDK ${{ matrix.java-version }}
Expand All @@ -42,6 +45,10 @@ jobs:
needs: build
runs-on: ubuntu-latest

# DEVEXP-1592 (temporary workaround): E2E tests are disabled for every Java version.
# TODO: remove this 'if' to re-enable them.
if: false

strategy:
matrix:
java-version: ['8', '11', '17', '21', '25']
Expand All @@ -61,16 +68,6 @@ jobs:
fetch-depth: 0
path: sinch-sdk-mockserver

- name: Install Docker Compose
run: |
sudo apt-get update
sudo apt-get install -y docker-compose

- name: Start mock servers with Docker Compose
run: |
cd sinch-sdk-mockserver
docker-compose up -d

- name: Link to feature files
run: |
ln -s ${{ github.workspace }}/sinch-sdk-mockserver/features client/src/test/resources
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ permissions:
pull-requests: write
jobs:
dependency-review:
if: ${{ github.repository_visibility == 'public' }}
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/samples-compilation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@ on: [push]

jobs:
build:
name: Examples Java ${{ matrix.java-version }}

runs-on: ubuntu-latest

strategy:
matrix:
java-version: ['21', '25']
maven-args: ['clean install -DskipTests=true -Dspotless.check.skip=true -DskipUTs -DskipITs']

steps:
- uses: actions/checkout@v3
- name: Set up JDK
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v3
with:
java-version: '21'
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: maven
- name: Building
run: |
mvn clean install -DskipTests=true -Dspotless.apply.skip=true -DskipUTs -DskipITs
mvn -B ${{ matrix.maven-args }} --file pom.xml
cd examples
./compile.sh

Expand Down
43 changes: 40 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,52 @@ All notable changes to the **Sinch Java SDK** are documented in this file.
> - `[tech]` — technical improvement

---
## v2.1.2 patch - 2026-08-13
## v2.2.0 - 2026-09-17

### SDK
- **[feature]** Default Apache HttpClient retry policy disabled in favor of a dedicated SDK implementation:
- honor the `Retry-After` response header on `HTTP 429` and fallback to exponential backoff if not present
- max retry changed from default Apache HttpClient (`1`) to SDK implementation: `3`
- **[feature]** `HTTP 429` retries extended to all endpoints (previously OAuth only), handled by a dedicated `RetryManager`
- **[feature]** Retry policy configurable from `SinchClient` via `setRetryConfiguration`: `retryPolicy` (`DEFAULT`, `RETRY_AFTER`, `BACKOFF`, `NONE`), `maxRetryCount` (default `3`), `exponentialBackoff` (default `4`)
- **[fix]** `HttpClientApache`: preserve the status code of an `ApiException` raised during authentication instead of discarding it

### Conversation
- **[feature]** [Events] Support `ReadMessageEvent` app event (WhatsApp only): use `ReadMessageEvent.READ_MESSAGE_EVENT`
- **[feature]** [Messages] [Choice] Support new `displayMode` field and `DisplayMode` enum
- **[tech]** [Templates V2] Synch with backend not returning body onto `delete`. No effect at SDK interface level

### Numbers
- Extend `NumberSinchEvent` class.
- **[feature]** Support new `NumberSinchEvent`: `ActiveNumberSinchEvent` and `NumberOrderSinchEvent`
- **[deprecation notice]** `NumberSinchEvent` is now a base class for new use cases from backend: `ActiveNumberSinchEvent` and `NumberOrderSinchEvent`.
- The following fields are deprecated at `NumberSinchEvent` level and will be removed in next major version:
- `getResourceId()`
- `getEventType()`
- `getStatus()`
- `getFailureCode()`
- `getInternalFailureCode()`
- Use their dedicated `ActiveNumberSinchEvent` and `NumberOrderSinchEvent` fields

### Voice
- **[feature]** Support `dice` field for `customCallout`: the callback URL receiving the DiCE event when the call is disconnected

### Build & CI
- **[tech]** Build `examples` across a Java version matrix (`21`, `25`) in GitHub Actions, replacing the single Java 21 build.
- **[dependency]** Bump `httpclient5.version` to `5.6.3` to solve a connection leak leading to connection pool exhaustion in `httpclient5` (CVE-2026-64607 and GHSA-hjcp-jmpx-g3qm).

### Tests
- **[test]** Fix HttpClient multipart test when returned boundary string contains '--' sequence


## v2.1.2 - 2026-08-13

### Build

- **[dependency]** Bump `jackson.version` to `2.21.5` to solve `@JsonView` bypass vulnerabilities in `jackson-databind` (CVE-2026-59889 and GHSA-mhm7-754m-9p8w).
- **[dependency]** Bump `httpclient5.version` to `5.6.2` to solve a denial-of-service vulnerability in the transitive `httpcore5` dependency (CVE-2026-54399).


## v2.1.1 patch - 2026-07-21
## v2.1.1 - 2026-07-21

### Build & CI

Expand Down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ To use Sinch services, you'll need a Sinch account and access keys. You can sign
- [Logging](#logging)
- [Handling Exceptions](#handling-exceptions)
- [Proxy configuration](#proxy-configuration)
- [Retry configuration](#retry-configuration)
- [Third-party dependencies](#third-party-dependencies)
- [Examples](#examples)
- [Changelog and Migration](#changelog--migration)
Expand Down Expand Up @@ -493,6 +494,54 @@ SinchClient client = new SinchClient(configuration);
```


## Retry configuration

When an API call or OAuth token request returns HTTP 429 (Too Many Requests), the SDK retries automatically. Configure this on `SinchClient`; the same settings apply to product API calls and token fetches.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `retryPolicy` | `RetryPolicy` | `DEFAULT` | `DEFAULT`: honor `Retry-After` when present, otherwise exponential backoff. `RETRY_AFTER`: retry only when a usable `Retry-After` header is present. `BACKOFF`: ignore `Retry-After` and use full-jitter exponential backoff. `NONE`: disable automatic retries. |
| `maxRetryCount` | `number` | `3` | Maximum retries after the first attempt before the error is surfaced to the caller. Must be a non-negative integer (`0` is allowed; decimals are rejected). |
| `exponentialBackoff` | `number` | `4` | Growth factor for the backoff ceiling (`1000ms * exponentialBackoff^attempt`). The wait is a random value between 0 and that ceiling. Must be a positive number (`> 0`; decimals are allowed). |

`Retry-After` may be a delay in seconds or an HTTP-date (RFC 7231). A small jitter (0–250 ms) is added so concurrent clients do not retry in lockstep. Invalid values are rejected.

### Retry settings

```java
import com.sinch.sdk.SinchClient;
import com.sinch.sdk.models.Configuration;
import com.sinch.sdk.models.RetryConfiguration;
import com.sinch.sdk.models.RetryPolicy;

...
Configuration configuration = Configuration.builder()
...
.setRetryConfiguration(
RetryConfiguration.builder()
.setRetryPolicy(RetryPolicy.BACKOFF)
.setMaxRetryCount(5)
.setExponentialBackoff(2)
.build())
.build();
SinchClient client = new SinchClient(configuration);
```

### Disable Retry Policy

To disable automatic retries (for example when an outer HTTP layer already honors `Retry-After`):

```java
Configuration configuration = Configuration.builder()
...
.setRetryConfiguration(
RetryConfiguration.builder()
.setRetryPolicy(RetryPolicy.NONE)
.build())
.build();
SinchClient client = new SinchClient(configuration);
```

## Third-party dependencies
The SDK relies on the following third-party dependencies:
- [Jackson](https://github.com/FasterXML/jackson-jakarta-rs-providers): provides JSON serialization/deserialization functionality.
Expand Down
2 changes: 1 addition & 1 deletion client/src/main/com/sinch/sdk/SDK.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
public class SDK {

public static final String NAME = "Sinch Java SDK";
public static final String VERSION = "2.2.0-dev";
public static final String VERSION = "2.3.0-dev";
public static final String AUXILIARY_FLAG = "";
}
15 changes: 10 additions & 5 deletions client/src/main/com/sinch/sdk/SinchClient.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.sinch.sdk;

import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.core.utils.StringUtil;
import com.sinch.sdk.domains.conversation.ConversationService;
import com.sinch.sdk.domains.numberlookup.NumberLookupService;
import com.sinch.sdk.domains.numbers.NumbersService;
import com.sinch.sdk.domains.sms.SMSService;
import com.sinch.sdk.domains.verification.VerificationService;
import com.sinch.sdk.domains.voice.VoiceService;
import com.sinch.sdk.http.DefaultRetryManager;
import com.sinch.sdk.http.HttpClientApache;
import com.sinch.sdk.models.Configuration;
import com.sinch.sdk.models.ConversationContext;
Expand Down Expand Up @@ -68,7 +70,7 @@ public class SinchClient {
private volatile VoiceService voice;
private volatile ConversationService conversation;
private volatile NumberLookupService lookup;
private volatile HttpClientApache httpClient;
private volatile HttpClient httpClient;

/**
* Create a Sinch Client instance based onto configuration
Expand Down Expand Up @@ -443,13 +445,16 @@ private Properties handlePropertiesFile(String fileName) {
return prop;
}

private HttpClientApache getHttpClient() {
HttpClientApache local = httpClient;
private HttpClient getHttpClient() {
HttpClient local = httpClient;
if (null == local || local.isClosed()) {
synchronized (this) {
local = httpClient;
if (null == local || local.isClosed()) {
local = new HttpClientApache(configuration.getHttpProxyConfiguration().orElse(null));
local =
new HttpClientApache(
configuration.getHttpProxyConfiguration().orElse(null),
new DefaultRetryManager(configuration.getRetryConfiguration().orElse(null)));

// set SDK User-Agent
String userAgent = formatSdkUserAgentHeader();
Expand Down Expand Up @@ -507,7 +512,7 @@ String formatAuxiliaryFlag(String auxiliaryFlag) {
*/
public void close() {
synchronized (this) {
HttpClientApache local = httpClient;
HttpClient local = httpClient;
httpClient = null;
numbers = null;
sms = null;
Expand Down
Loading
Loading