Repository: googleapis/google-http-java-client
Pull request #1992 (2024-08-16 18:32:17)

deps: update ossf/scorecard-action action to v2.4.0
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ossf/scorecard-action](https://togithub.com/ossf/scorecard-action) | action | minor | `v2.1.2` -> `v2.4.0` |

---

### Release Notes

<details>
<summary>ossf/scorecard-action (ossf/scorecard-action)</summary>

### [`v2.4.0`](https://togithub.com/ossf/scorecard-action/releases/tag/v2.4.0)

[Compare Source](https://togithub.com/ossf/scorecard-action/compare/v2.3.3...v2.4.0)

#### What's Changed

This update bumps the Scorecard version to the v5 release. For a complete list of changes, please refer to the [v5.0.0 release notes](https://togithub.com/ossf/scorecard/releases/tag/v5.0.0). Of special note to Scorecard Action is the Maintainer Annotation feature, which can be used to suppress some Code Scanning false positives. Alerts will not be generated for any Scorecard Check with an annotation.

-   :seedling: Bump github.com/ossf/scorecard/v5 from v5.0.0-rc2 to v5.0.0 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1410](https://togithub.com/ossf/scorecard-action/pull/1410)
-   :bug: lower license sarif alert threshold to 9 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1411](https://togithub.com/ossf/scorecard-action/pull/1411)

##### Documentation

-   docs: dogfooding badge by [@&#8203;jkowalleck](https://togithub.com/jkowalleck) in [https://github.com/ossf/scorecard-action/pull/1399](https://togithub.com/ossf/scorecard-action/pull/1399)

#### New Contributors

-   [@&#8203;jkowalleck](https://togithub.com/jkowalleck) made their first contribution in [https://github.com/ossf/scorecard-action/pull/1399](https://togithub.com/ossf/scorecard-action/pull/1399)

**Full Changelog**: https://github.com/ossf/scorecard-action/compare/v2.3.3...v2.4.0

### [`v2.3.3`](https://togithub.com/ossf/scorecard-action/releases/tag/v2.3.3)

[Compare Source](https://togithub.com/ossf/scorecard-action/compare/v2.3.2...v2.3.3)

> \[!NOTE]\
> There is no v2.3.2 release as a step was skipped in the release process. This was fixed and re-released under the v2.3.3 tag

#### What's Changed

-   :seedling: Bump github.com/ossf/scorecard/v4 (v4.13.1) to github.com/ossf/scorecard/v5 (v5.0.0-rc1) by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1366](https://togithub.com/ossf/scorecard-action/pull/1366)
-   :seedling: Bump github.com/ossf/scorecard/v5 from v5.0.0-rc1 to v5.0.0-rc2 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1374](https://togithub.com/ossf/scorecard-action/pull/1374)
-   :seedling: Bump github.com/ossf/scorecard/v5 from v5.0.0-rc2 to v5.0.0-rc2.0.20240509182734-7ce860946928 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1377](https://togithub.com/ossf/scorecard-action/pull/1377)

For a full changelist of what these include, see the [v5.0.0-rc1](https://togithub.com/ossf/scorecard/releases/tag/v5.0.0-rc1) and [v5.0.0-rc2](https://togithub.com/ossf/scorecard/releases/tag/v5.0.0-rc2) release notes.

##### Documentation

-   :book: Move token discussion out of main README. by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1279](https://togithub.com/ossf/scorecard-action/pull/1279)
-   :book: link to `ossf/scorecard` workflow instead of maintaining an example by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1352](https://togithub.com/ossf/scorecard-action/pull/1352)
-   :book: update api links to new scorecard.dev site by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1376](https://togithub.com/ossf/scorecard-action/pull/1376)

**Full Changelog**: https://github.com/ossf/scorecard-action/compare/v2.3.1...v2.3.3

### [`v2.3.2`](https://togithub.com/ossf/scorecard-action/compare/v2.3.1...v2.3.2)

[Compare Source](https://togithub.com/ossf/scorecard-action/compare/v2.3.1...v2.3.2)

### [`v2.3.1`](https://togithub.com/ossf/scorecard-action/releases/tag/v2.3.1)

[Compare Source](https://togithub.com/ossf/scorecard-action/compare/v2.3.0...v2.3.1)

#### What's Changed

-   :seedling: Bump github.com/ossf/scorecard/v4 from v4.13.0 to v4.13.1 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1282](https://togithub.com/ossf/scorecard-action/pull/1282)
    -   Adds additional Fuzzing detection and fixes a SAST bug related to detecting CodeQL. For a full changelist of what this includes, see the  [v4.13.1](https://togithub.com/ossf/scorecard/releases/tag/v4.13.1) release notes

**Full Changelog**: https://github.com/ossf/scorecard-action/compare/v2.3.0...v2.3.1

### [`v2.3.0`](https://togithub.com/ossf/scorecard-action/releases/tag/v2.3.0)

[Compare Source](https://togithub.com/ossf/scorecard-action/compare/v2.2.0...v2.3.0)

#### What's Changed

-   :seedling: Bump github.com/ossf/scorecard/v4 from v4.11.0 to v4.13.0 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1270](https://togithub.com/ossf/scorecard-action/pull/1270)
    -   For a full changelist of what this includes, see the [v4.12.0](https://togithub.com/ossf/scorecard/releases/tag/v4.12.0) and [v4.13.0](https://togithub.com/ossf/scorecard/releases/tag/v4.13.0) release notes
-   :sparkles: Send rekor tlog index to webapp when publishing results by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1169](https://togithub.com/ossf/scorecard-action/pull/1169)
-   :bug: Prevent url clipping for GHES instances by [@&#8203;rajbos](https://togithub.com/rajbos) in [https://github.com/ossf/scorecard-action/pull/1225](https://togithub.com/ossf/scorecard-action/pull/1225)

##### Documentation

-   :book: Update access rights needed to see the results in code scanning by [@&#8203;rajbos](https://togithub.com/rajbos) in [https://github.com/ossf/scorecard-action/pull/1229](https://togithub.com/ossf/scorecard-action/pull/1229)
-   :book: Add package comments. by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1221](https://togithub.com/ossf/scorecard-action/pull/1221)
-   :book: Add SECURITY.md file by [@&#8203;david-a-wheeler](https://togithub.com/david-a-wheeler) in [https://github.com/ossf/scorecard-action/pull/1250](https://togithub.com/ossf/scorecard-action/pull/1250)
-   :book: Fix typo in token input docs by [@&#8203;aabouzaid](https://togithub.com/aabouzaid) in [https://github.com/ossf/scorecard-action/pull/1258](https://togithub.com/ossf/scorecard-action/pull/1258)

#### New Contributors

-   [@&#8203;david-a-wheeler](https://togithub.com/david-a-wheeler) made their first contribution in [https://github.com/ossf/scorecard-action/pull/1250](https://togithub.com/ossf/scorecard-action/pull/1250)
-   [@&#8203;aabouzaid](https://togithub.com/aabouzaid) made their first contribution in [https://github.com/ossf/scorecard-action/pull/1258](https://togithub.com/ossf/scorecard-action/pull/1258)

**Full Changelog**: https://github.com/ossf/scorecard-action/compare/v2.2.0...v2.3.0

### [`v2.2.0`](https://togithub.com/ossf/scorecard-action/releases/tag/v2.2.0)

[Compare Source](https://togithub.com/ossf/scorecard-action/compare/v2.1.3...v2.2.0)

#### What's Changed

-   :seedling: Bump github.com/ossf/scorecard/v4 from v4.10.5 to v4.11.0 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1192](https://togithub.com/ossf/scorecard-action/pull/1192)

#### Scorecard Result Viewer

Thanks to contributions from [@&#8203;cynthia-sg](https://togithub.com/cynthia-sg) and [@&#8203;tegioz](https://togithub.com/tegioz) at [CLOMonitor](https://togithub.com/cncf/clomonitor), there is a new Scorecard Result visualization page at `https://securityscorecards.dev/viewer/?uri=<project-url>`.

-   [https://github.com/ossf/scorecard-webapp/pull/406](https://togithub.com/ossf/scorecard-webapp/pull/406)
-   [https://github.com/ossf/scorecard-webapp/pull/422](https://togithub.com/ossf/scorecard-webapp/pull/422)

As an example, you can see our own score visualized [here](https://securityscorecards.dev/viewer/?uri=github.com/ossf/scorecard)
Checkout our [README](https://togithub.com/ossf/scorecard-action/blob/08b4669551908b1024bb425080c797723083c031/README.md#scorecard-badge) to learn how to link your README badge to the new visualization page.

#### Publishing Results

This release contains two fixes which will improve the user experience when `publish_results` is `true`

-   Runs that fail our [workflow restrictions](https://togithub.com/ossf/scorecard-action/blob/08b4669551908b1024bb425080c797723083c031/README.md#workflow-restrictions) will fail with a 400 response indicating the problem, instead of a vague 500 status. ([https://github.com/ossf/scorecard-action/pull/1156](https://togithub.com/ossf/scorecard-action/pull/1156), resolved [https://github.com/ossf/scorecard-action/issues/1150](https://togithub.com/ossf/scorecard-action/issues/1150))
-   Scorecard action will retry when signing results and submitting them to our web API. This should help with flakiness from connection failures. ([https://github.com/ossf/scorecard-action/pull/1191](https://togithub.com/ossf/scorecard-action/pull/1191))

#### Docs

-   📖 Update README to accept fine-grained tokens by [@&#8203;pnacht](https://togithub.com/pnacht) in [https://github.com/ossf/scorecard-action/pull/1175](https://togithub.com/ossf/scorecard-action/pull/1175)
-   📖 Update installation instructions to match current GitHub UI  by [@&#8203;joycebrum](https://togithub.com/joycebrum) in [https://github.com/ossf/scorecard-action/pull/1153](https://togithub.com/ossf/scorecard-action/pull/1153)
-   📖 Document the GitHub action workflow restrictions when publishing results. by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in

#### New Contributors

-   [@&#8203;bobcallaway](https://togithub.com/bobcallaway) made their first contribution in [https://github.com/ossf/scorecard-action/pull/1140](https://togithub.com/ossf/scorecard-action/pull/1140)
-   [@&#8203;pnacht](https://togithub.com/pnacht) made their first contribution in [https://github.com/ossf/scorecard-action/pull/1175](https://togithub.com/ossf/scorecard-action/pull/1175)

**Full Changelog**: https://github.com/ossf/scorecard-action/compare/v2.1.3...v2.2.0

### [`v2.1.3`](https://togithub.com/ossf/scorecard-action/releases/tag/v2.1.3)

[Compare Source](https://togithub.com/ossf/scorecard-action/compare/v2.1.2...v2.1.3)

#### What's Changed

-   🌱 Bump github.com/ossf/scorecard/v4 from 4.10.2 to 4.10.5 by [@&#8203;spencerschrock](https://togithub.com/spencerschrock) in [https://github.com/ossf/scorecard-action/pull/1111](https://togithub.com/ossf/scorecard-action/pull/1111)

##### Bug Fixes

-   Invalid SARIF files from a bug in scorecard
    -   [#&#8203;1076](https://togithub.com/ossf/scorecard-action/issues/1076), [#&#8203;1094](https://togithub.com/ossf/scorecard-action/issues/1094)
-   Vulnerabilities check crashes if a vulnerable dependency is found via OSVScanner
    -   [#&#8203;1092](https://togithub.com/ossf/scorecard-action/issues/1092)
-   Scorecard action not reporting binary artifacts in the repo
    -   [#&#8203;1116](https://togithub.com/ossf/scorecard-action/issues/1116)

**Full Scorecard Changelog**: https://github.com/ossf/scorecard/compare/v4.10.2...v4.10.5

**Full Changelog**: https://github.com/ossf/scorecard-action/compare/v2.1.2...v2.1.3

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View the [repository job log](https://developer.mend.io/github/googleapis/google-http-java-client).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4yNi4xIiwidXBkYXRlZEluVmVyIjoiMzguMjYuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->


dpebot: /gcbrun

Patch:
diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml
index 7a106d00..18d92e5a 100644
--- a/.github/workflows/auto-release.yaml
+++ b/.github/workflows/auto-release.yaml
@@ -21,7 +21,7 @@ jobs:
     runs-on: ubuntu-latest
     if: contains(github.head_ref, 'release-please')
     steps:
-    - uses: actions/github-script@v6
+    - uses: actions/github-script@v7
       with:
         github-token: ${{secrets.YOSHI_APPROVER_TOKEN}}
         debug: true
diff --git a/.github/workflows/ci-java7.yaml b/.github/workflows/ci-java7.yaml
index 2c8257d4..4dec872e 100644
--- a/.github/workflows/ci-java7.yaml
+++ b/.github/workflows/ci-java7.yaml
@@ -24,7 +24,7 @@ jobs:
     name: "units (7)"
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v1
       # setup-java v2 or higher does not have version 1.7
       with:
@@ -55,7 +55,7 @@ jobs:
         # dailymotion-simple-cmdline-sample and google-http-client-assembly depend on
         # google-http-client-jackson2 
         mvn --batch-mode --show-version -ntp test \
-            --projects '!google-http-client-jackson2,!google-http-client-appengine,!samples/dailymotion-simple-cmdline-sample,!google-http-client-assembly' \
+            --projects '!google-http-client-jackson2,!google-http-client-appengine,!samples/dailymotion-simple-cmdline-sample,!google-http-client-assembly,!google-http-client-apache-v5' \
             -Dclirr.skip=true -Denforcer.skip=true -Dmaven.javadoc.skip=true \
             -Dgcloud.download.skip=true -T 1C \
             -Dproject.surefire.version=2.22.2 \
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 32feef7d..0e87e017 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -27,7 +27,7 @@ jobs:
       matrix:
         java: [8, 11, 17]
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v3
       with:
         distribution: temurin
@@ -41,7 +41,7 @@ jobs:
     steps:
     - name: Support longpaths
       run: git config --system core.longpaths true
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v3
       with:
         distribution: temurin
@@ -56,7 +56,7 @@ jobs:
       matrix:
         java: [8, 11, 17]
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v3
       with:
         distribution: temurin
@@ -66,7 +66,7 @@ jobs:
   lint:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v3
       with:
         distribution: temurin
@@ -78,7 +78,7 @@ jobs:
   clirr:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v3
       with:
         distribution: temurin
@@ -94,7 +94,7 @@ jobs:
     name: "units (21)"
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v3
       with:
         java-version: 21
diff --git a/.github/workflows/downstream.yaml b/.github/workflows/downstream.yaml
index c4be85ea..8681fe5f 100644
--- a/.github/workflows/downstream.yaml
+++ b/.github/workflows/downstream.yaml
@@ -133,7 +133,7 @@ jobs:
         - workflow-executions
         - workflows
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-java@v3
       with:
         distribution: zulu
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 7dd91434..f153dddd 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -32,12 +32,12 @@ jobs:
 
     steps:
       - name: "Checkout code"
-        uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
+        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
         with:
           persist-credentials: false
 
       - name: "Run analysis"
-        uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
+        uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
         with:
           results_file: results.sarif
           results_format: sarif
@@ -59,7 +59,7 @@ jobs:
       # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
       # format to the repository Actions tab.
       - name: "Upload artifact"
-        uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
+        uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
         with:
           name: SARIF file
           path: results.sarif
@@ -67,6 +67,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
+        uses: github/codeql-action/upload-sarif@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
         with:
           sarif_file: results.sarif
diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg
index ad109968..54dca4f2 100644
--- a/.kokoro/presubmit/graalvm-native-a.cfg
+++ b/.kokoro/presubmit/graalvm-native-a.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-public-resources/graalvm_a:1.9.0"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_a:1.11.0"
 }
 
 env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg
index e5229b14..8b05c77f 100644
--- a/.kokoro/presubmit/graalvm-native-b.cfg
+++ b/.kokoro/presubmit/graalvm-native-b.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-public-resources/graalvm_b:1.9.0"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_b:1.11.0"
 }
 
 env_vars: {
diff --git a/docs/http-transport.md b/docs/http-transport.md
index 0167db4a..b6d94cce 100644
--- a/docs/http-transport.md
+++ b/docs/http-transport.md
@@ -19,8 +19,12 @@ There are three built-in low-level HTTP transports:
 
 1. [`NetHttpTransport`][net-http-transport]: based on [`HttpURLConnection`][http-url-connection]
 that is found in all Java SDKs, and thus usually the simplest choice.
+1. [`Apache5HttpTransport`][apache-http-transport]: based on the popular
+   [Apache 5.x HttpClient][apache5-http-client] that allows for more customization.
 1. [`ApacheHttpTransport`][apache-http-transport]: based on the popular
-[Apache HttpClient][apache-http-client] that allows for more customization.
+[Apache 4.x HttpClient][apache-http-client] that allows for more customization. Note that this transport implementation
+relies on [Apache 4.x HttpCore][apache-http-core] which has reached end of life. It is recommended to use
+[`Apache5HttpTransport`][apache-http-transport] instead.
 1. [`UrlFetchTransport`][url-fetch-transport]: based on the [URL Fetch Java API][url-fetch] in the
 Google App Engine SDK.
 
@@ -124,7 +128,10 @@ HttpRequestFactory requestFactory = transport.createRequestFactory(new MyInitial
 [net-http-transport]: https://googleapis.dev/java/google-http-client/latest/index.html?com/google/api/client/http/javanet/NetHttpTransport.html
 [http-url-connection]: http://docs.oracle.com/javase/7/docs/api/java/net/HttpURLConnection.html
 [apache-http-transport]: https://googleapis.dev/java/google-http-client/latest/index.html?com/google/api/client/http/apache/v2/ApacheHttpTransport.html
-[apache-http-client]: http://hc.apache.org/httpcomponents-client-ga/index.html
+[apache5-http-transport]: https://github.com/googleapis/google-http-java-client/blob/de8743587d1415e8a6046096ac1fc0a5e81490c3/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java
+[apache-http-client]: https://hc.apache.org/httpcomponents-client-4.5.x/index.html
+[apache-http-core]: https://hc.apache.org/httpcomponents-core-4.4.x/index.html
+[apache5-http-client]: https://hc.apache.org/httpcomponents-client-5.3.x/index.html
 [url-fetch-transport]: https://googleapis.dev/java/google-http-client/latest/index.html?com/google/api/client/extensions/appengine/http/UrlFetchTransport.html
 [url-fetch]: https://cloud.google.com/appengine/docs/java/javadoc/com/google/appengine/api/urlfetch/package-summary
 [logger]: https://docs.oracle.com/javase/7/docs/api/java/util/logging/Logger.html
diff --git a/google-http-client-apache-v5/pom.xml b/google-http-client-apache-v5/pom.xml
new file mode 100644
index 00000000..a6789797
--- /dev/null
+++ b/google-http-client-apache-v5/pom.xml
@@ -0,0 +1,116 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.google.http-client</groupId>
+    <artifactId>google-http-client-parent</artifactId>
+    <version>1.44.3-SNAPSHOT</version><!-- {x-version-update:google-http-client-parent:current} -->
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <artifactId>google-http-client-apache-v5</artifactId>
+  <version>1.44.3-SNAPSHOT</version><!-- {x-version-update:google-http-client-apache-v5:current} -->
+  <name>Apache HTTP transport v5 for the Google HTTP Client Library for Java.</name>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <configuration>
+          <links>
+            <link>https://download.oracle.com/javase/7/docs/api/</link>
+          </links>
+          <doctitle>${project.name} ${project.version}</doctitle>
+          <windowtitle>${project.artifactId} ${project.version}</windowtitle>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-source-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <version>3.3.0</version>
+        <executions>
+          <execution>
+            <id>add-test-source</id>
+            <phase>generate-test-sources</phase>
+            <goals>
+              <goal>add-test-source</goal>
+            </goals>
+            <configuration>
+              <sources>
+                <source>target/generated-test-sources</source>
+              </sources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+            <manifestEntries>
+              <Automatic-Module-Name>com.google.api.client.http.apache.v5</Automatic-Module-Name>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>5.1.9</version>
+        <executions>
+          <execution>
+            <id>bundle-manifest</id>
+            <phase>process-classes</phase>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.13.0</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>com.google.http-client</groupId>
+      <artifactId>google-http-client</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.httpcomponents</groupId>
+          <artifactId>httpcore</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.httpcomponents</groupId>
+          <artifactId>httpclient</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents.client5</groupId>
+      <artifactId>httpclient5</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents.core5</groupId>
+      <artifactId>httpcore5</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ContentEntity.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ContentEntity.java
new file mode 100644
index 00000000..4a5ab84e
--- /dev/null
+++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ContentEntity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.api.client.http.apache.v5;
+
+import com.google.api.client.util.Preconditions;
+import com.google.api.client.util.StreamingContent;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
+
+/**
+ * Translation class to make google-http-client entity conform with Apache 5.x {@link
+ * AbstractHttpEntity}
+ */
+final class Apache5ContentEntity extends AbstractHttpEntity {
+
+  /** Content length or less than zero if not known. */
+  private final long contentLength;
+
+  /** Streaming content. */
+  private final StreamingContent streamingContent;
+
+  /**
+   * @param contentLength content length or less than zero if not known
+   * @param streamingContent streaming content
+   */
+  Apache5ContentEntity(
+      long contentLength,
+      StreamingContent streamingContent,
+      String contentType,
+      String contentEncoding) {
+    super(contentType, contentEncoding, contentLength == -1);
+    this.contentLength = contentLength;
+    this.streamingContent = Preconditions.checkNotNull(streamingContent);
+  }
+
+  @Override
+  public InputStream getContent() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public long getContentLength() {
+    return contentLength;
+  }
+
+  @Override
+  public boolean isRepeatable() {
+    return false;
+  }
+
+  @Override
+  public boolean isStreaming() {
+    return true;
+  }
+
+  @Override
+  public void writeTo(OutputStream out) throws IOException {
+    if (contentLength != 0) {
+      streamingContent.writeTo(out);
+    }
+  }
+
+  @Override
+  public void close() throws IOException {}
+}
diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpRequest.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpRequest.java
new file mode 100644
index 00000000..99d6eca8
--- /dev/null
+++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.api.client.http.apache.v5;
+
+import com.google.api.client.http.LowLevelHttpRequest;
+import com.google.api.client.http.LowLevelHttpResponse;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.routing.RoutingSupport;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.util.Timeout;
+
+public final class Apache5HttpRequest extends LowLevelHttpRequest {
+
+  private final HttpUriRequestBase request;
+
+  private final RequestConfig.Builder requestConfig;
+
+  private final HttpClient httpClient;
+
+  Apache5HttpRequest(HttpClient httpClient, HttpUriRequestBase request) {
+    this.httpClient = httpClient;
+    this.request = request;
+    // disable redirects as google-http-client handles redirects
+    this.requestConfig = RequestConfig.custom().setRedirectsEnabled(false);
+  }
+
+  @Override
+  public void addHeader(String name, String value) {
+    request.addHeader(name, value);
+  }
+
+  @Override
+  public void setTimeout(int connectTimeout, int readTimeout) throws IOException {
+    requestConfig
+        .setConnectTimeout(Timeout.of(connectTimeout, TimeUnit.MILLISECONDS))
+        // ResponseTimeout behaves the same as 4.x's SocketTimeout
+        .setResponseTimeout(Timeout.of(readTimeout, TimeUnit.MILLISECONDS));
+  }
+
+  @Override
+  public LowLevelHttpResponse execute() throws IOException {
+    if (getStreamingContent() != null) {
+      Apache5ContentEntity entity =
+          new Apache5ContentEntity(
+              getContentLength(), getStreamingContent(), getContentType(), getContentEncoding());
+      request.setEntity(entity);
+    }
+    request.setConfig(requestConfig.build());
+    HttpHost target;
+    try {
+      target = RoutingSupport.determineHost(request);
+    } catch (HttpException e) {
+      throw new ClientProtocolException("The request's host is invalid.", e);
+    }
+    // we use a null context so the client creates the default one internally
+    ClassicHttpResponse httpResponse = httpClient.executeOpen(target, request, null);
+    return new Apache5HttpResponse(request, httpResponse);
+  }
+}
diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpResponse.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpResponse.java
new file mode 100644
index 00000000..1574c8c8
--- /dev/null
+++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpResponse.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.api.client.http.apache.v5;
+
+import com.google.api.client.http.LowLevelHttpResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Logger;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.message.StatusLine;
+
+final class Apache5HttpResponse extends LowLevelHttpResponse {
+
+  private static final Logger LOGGER = Logger.getLogger(Apache5HttpResponse.class.getName());
+  private final HttpUriRequestBase request;
+  private final ClassicHttpResponse response;
+  private final Header[] allHeaders;
+  private final HttpEntity entity;
+
+  Apache5HttpResponse(HttpUriRequestBase request, ClassicHttpResponse response) {
+    this.request = request;
+    this.response = response;
+    this.allHeaders = response.getHeaders();
+    this.entity = response.getEntity();
+  }
+
+  @Override
+  public int getStatusCode() {
+    return response.getCode();
+  }
+
+  @Override
+  public InputStream getContent() throws IOException {
+    return new Apache5ResponseContent(entity.getContent(), response);
+  }
+
+  @Override
+  public String getContentEncoding() {
+    return entity != null ? entity.getContentEncoding() : null;
+  }
+
+  @Override
+  public long getContentLength() {
+    return entity == null ? -1 : entity.getContentLength();
+  }
+
+  @Override
+  public String getContentType() {
+    return entity == null ? null : entity.getContentType();
+  }
+
+  @Override
+  public String getReasonPhrase() {
+    return response.getReasonPhrase();
+  }
+
+  @Override
+  public String getStatusLine() {
+    return new StatusLine(response).toString();
+  }
+
+  public String getHeaderValue(String name) {
+    return response.getLastHeader(name).getValue();
+  }
+
+  @Override
+  public int getHeaderCount() {
+    return allHeaders.length;
+  }
+
+  @Override
+  public String getHeaderName(int index) {
+    return allHeaders[index].getName();
+  }
+
+  @Override
+  public String getHeaderValue(int index) {
+    return allHeaders[index].getValue();
+  }
+
+  /** Aborts execution of the request. */
+  @Override
+  public void disconnect() throws IOException {
+    request.abort();
+    response.close();
+  }
+}
diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java
new file mode 100644
index 00000000..868a2cf9
--- /dev/null
+++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.api.client.http.apache.v5;
+
+import com.google.api.client.http.HttpMethods;
+import com.google.api.client.http.HttpTransport;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.classic.methods.HttpDelete;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpHead;
+import org.apache.hc.client5.http.classic.methods.HttpOptions;
+import org.apache.hc.client5.http.classic.methods.HttpPatch;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.classic.methods.HttpPut;
+import org.apache.hc.client5.http.classic.methods.HttpTrace;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
+import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.io.ModalCloseable;
+
+/**
+ * Thread-safe HTTP transport based on the Apache HTTP Client library.
+ *
+ * <p>Implementation is thread-safe, as long as any parameter modification to the {@link
+ * #getHttpClient() Apache HTTP Client} is only done at initialization time. For maximum efficiency,
+ * applications should use a single globally-shared instance of the HTTP transport.
+ *
+ * <p>Default settings are specified in {@link #newDefaultHttpClient()}. Use the {@link
+ * #Apache5HttpTransport(HttpClient)} constructor to override the Apache HTTP Client used. Please
+ * read the <a
+ * href="https://github.com/apache/httpcomponents-client/blob/f898f1aca38f77f62a007856a674629cae5a02e6/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java">
+ * Apache HTTP Client 5.x configuration example</a> for more complex configuration options.
+ */
+public final class Apache5HttpTransport extends HttpTransport {
+
+  /** Apache HTTP client. */
+  private final HttpClient httpClient;
+
+  /** If the HTTP client uses mTLS channel. */
+  private final boolean isMtls;
+
+  /** Constructor that uses {@link #newDefaultHttpClient()} for the Apache HTTP client. */
+  public Apache5HttpTransport() {
+    this(newDefaultHttpClient(), false);
+  }
+
+  /**
+   * Constructor that allows an alternative Apache HTTP client to be used.
+   *
+   * <p>If you choose to provide your own Apache HttpClient implementation, be sure that
+   *
+   * <ul>
+   *   <li>HTTP version is set to 1.1.
+   *   <li>Retries are disabled (google-http-client handles retries).
+   * </ul>
+   *
+   * @param httpClient Apache HTTP client to use
+   */
+  public Apache5HttpTransport(HttpClient httpClient) {
+    this.httpClient = httpClient;
+    this.isMtls = false;
+  }
+
+  /**
+   * {@link Beta} <br>
+   * Constructor that allows an alternative CLoseable Apache HTTP client to be used.
+   *
+   * <p>If you choose to provide your own Apache HttpClient implementation, be sure that
+   *
+   * <ul>
+   *   <li>HTTP version is set to 1.1.
+   *   <li>Retries are disabled (google-http-client handles retries).
+   *   <li>Redirects are disabled (google-http-client handles retries).
+   * </ul>
+   *
+   * @param httpClient Apache HTTP client to use
+   * @param isMtls If the HTTP client is mutual TLS
+   */
+  @Beta
+  public Apache5HttpTransport(HttpClient httpClient, boolean isMtls) {
+    this.httpClient = httpClient;
+    this.isMtls = isMtls;
+  }
+
+  /**
+   * Creates a new instance of the Apache HTTP client that is used by the {@link
+   * #Apache5HttpTransport()} constructor.
+   *
+   * <p>Settings:
+   *
+   * <ul>
+   *   <li>The client connection manager is set to {@link PoolingHttpClientConnectionManager}.
+   *   <li>The retry mechanism is turned off using {@link
+   *       HttpClientBuilder#disableAutomaticRetries()}.
+   *   <li>Redirects are turned off using {@link HttpClientBuilder#disableRedirectHandling}.
+   *   <li>The route planner uses {@link SystemDefaultRoutePlanner} with {@link
+   *       ProxySelector#getDefault()}, which uses the proxy settings from <a
+   *       href="https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html">system
+   *       properties</a>.
+   * </ul>
+   *
+   * @return new instance of the Apache HTTP client
+   */
+  public static HttpClient newDefaultHttpClient() {
+    return newDefaultHttpClientBuilder().build();
+  }
+
+  /**
+   * Creates a new Apache HTTP client builder that is used by the {@link #Apache5HttpTransport()}
+   * constructor.
+   *
+   * <p>Settings:
+   *
+   * <ul>
+   *   <li>The client connection manager is set to {@link PoolingHttpClientConnectionManager}.
+   *   <li>The retry mechanism is turned off using {@link
+   *       HttpClientBuilder#disableAutomaticRetries()}.
+   *   <li>Redirects are turned off using {@link HttpClientBuilder#disableRedirectHandling}.
+   *   <li>The route planner uses {@link SystemDefaultRoutePlanner} with {@link
+   *       ProxySelector#getDefault()}, which uses the proxy settings from <a
+   *       href="http://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html">system
+   *       properties</a>.
+   * </ul>
+   *
+   * @return new instance of the Apache HTTP client builder
+   */
+  public static HttpClientBuilder newDefaultHttpClientBuilder() {
+    PoolingHttpClientConnectionManager connectionManager =
+        PoolingHttpClientConnectionManagerBuilder.create()
+            .setSSLSocketFactory(SSLConnectionSocketFactory.getSocketFactory())
+            .setMaxConnTotal(200)
+            .setMaxConnPerRoute(20)
+            .setDefaultConnectionConfig(
+                ConnectionConfig.custom().setTimeToLive(-1, TimeUnit.MILLISECONDS).build())
+            .build();
+
+    return HttpClients.custom()
+        .useSystemProperties()
+        .setConnectionManager(connectionManager)
+        .setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault()))
+        .disableRedirectHandling()
+        .disableAutomaticRetries();
+  }
+
+  @Override
+  public boolean supportsMethod(String method) {
+    return true;
+  }
+
+  @Override
+  protected Apache5HttpRequest buildRequest(String method, String url) {
+    HttpUriRequestBase requestBase;
+    if (method.equals(HttpMethods.DELETE)) {
+      requestBase = new HttpDelete(url);
+    } else if (method.equals(HttpMethods.GET)) {
+      requestBase = new HttpGet(url);
+    } else if (method.equals(HttpMethods.HEAD)) {
+      requestBase = new HttpHead(url);
+    } else if (method.equals(HttpMethods.PATCH)) {
+      requestBase = new HttpPatch(url);
+    } else if (method.equals(HttpMethods.POST)) {
+      requestBase = new HttpPost(url);
+    } else if (method.equals(HttpMethods.PUT)) {
+      requestBase = new HttpPut(url);
+    } else if (method.equals(HttpMethods.TRACE)) {
+      requestBase = new HttpTrace(url);
+    } else if (method.equals(HttpMethods.OPTIONS)) {
+      requestBase = new HttpOptions(url);
+    } else {
+      requestBase = new HttpUriRequestBase(Preconditions.checkNotNull(method), URI.create(url));
+    }
+    return new Apache5HttpRequest(httpClient, requestBase);
+  }
+
+  /**
+   * Gracefully shuts down the connection manager and releases allocated resources. This closes all
+   * connections, whether they are currently used or not.
+   */
+  @Override
+  public void shutdown() throws IOException {
+    if (httpClient instanceof ModalCloseable) {
+      ((ModalCloseable) httpClient).close(CloseMode.GRACEFUL);
+    }
+    // otherwise no-op
+  }
+
+  /** Returns the Apache HTTP client. */
+  public HttpClient getHttpClient() {
+    return httpClient;
+  }
+
+  /** Returns if the underlying HTTP client is mTLS. */
+  @Override
+  public boolean isMtls() {
+    return isMtls;
+  }
+}
diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ResponseContent.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ResponseContent.java
new file mode 100644
index 00000000..c2d3091d
--- /dev/null
+++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5ResponseContent.java
@@ -0,0 +1,75 @@
+package com.google.api.client.http.apache.v5;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpResponse;
+
+/**
+ * Class that wraps an {@link org.apache.hc.core5.http.HttpEntity}'s content {@link InputStream}
+ * along with the {@link ClassicHttpResponse} that contains this entity. The main purpose is to be
+ * able to close the response as well as the content input stream when {@link #close()} is called,
+ * in order to not break the existing contract with clients using apache v4 that only required them
+ * to close the input stream to clean up all resources.
+ */
+public class Apache5ResponseContent extends InputStream {
+  private final ClassicHttpResponse response;
+  private final InputStream wrappedStream;
+
+  public Apache5ResponseContent(InputStream wrappedStream, ClassicHttpResponse response) {
+    this.response = response;
+    this.wrappedStream = wrappedStream;
+  }
+
+  @Override
+  public int read() throws IOException {
+    return wrappedStream.read();
+  }
+
+  @Override
+  public int read(byte b[]) throws IOException {
+    return wrappedStream.read(b);
+  }
+
+  @Override
+  public int read(byte b[], int off, int len) throws IOException {
+    return wrappedStream.read(b, off, len);
+  }
+
+  @Override
+  public long skip(long n) throws IOException {
+    return wrappedStream.skip(n);
+  }
+
+  @Override
+  public int available() throws IOException {
+    return wrappedStream.available();
+  }
+
+  @Override
+  public synchronized void mark(int readlimit) {
+    wrappedStream.mark(readlimit);
+  }
+
+  @Override
+  public synchronized void reset() throws IOException {
+    wrappedStream.reset();
+  }
+
+  @Override
+  public void close() throws IOException {
+    wrappedStream.close();
+    response.close();
+  }
+
+  @Override
+  public boolean markSupported() {
+    return wrappedStream.markSupported();
+  }
+
+  @VisibleForTesting
+  HttpResponse getResponse() {
+    return response;
+  }
+}
diff --git a/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/package-info.java b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/package-info.java
new file mode 100644
index 00000000..223edc82
--- /dev/null
+++ b/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/package-info.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/** HTTP Transport library for Google API's based on Apache HTTP Client/Core version 5.x */
+package com.google.api.client.http.apache.v5;
diff --git a/google-http-client-assembly/classpath-include b/google-http-client-assembly/classpath-include
index c1bd8032..c7bbd573 100644
--- a/google-http-client-assembly/classpath-include
+++ b/google-http-client-assembly/classpath-include
@@ -7,8 +7,8 @@
 	<classpathentry kind="lib" path="libs/google-http-client-xml-${project.version}.jar" sourcepath="libs-sources/google-http-client-xml-${project.version}-sources.jar"/>
 	<classpathentry kind="lib" path="libs/commons-logging-${project.commons-logging.version}.jar" sourcepath="libs-sources/commons-logging-${project.commons-logging.version}-sources.jar"/>
 	<classpathentry kind="lib" path="libs/gson-${project.gson.version}.jar" sourcepath="libs-sources/gson-${project.gson.version}-sources.jar"/>
-	<classpathentry kind="lib" path="libs/httpclient-${project.httpclient.version}.jar" sourcepath="libs-sources/httpclient-${project.httpclient.version}-sources.jar"/>
-	<classpathentry kind="lib" path="libs/httpcore-${project.httpcore.version}.jar" sourcepath="libs-sources/httpcore-${project.httpcore.version}-sources.jar"/>
+	<classpathentry kind="lib" path="libs/httpclient-${project.apache-httpclient-4.version}.jar" sourcepath="libs-sources/httpclient-${project.apache-httpclient-4.version}-sources.jar"/>
+	<classpathentry kind="lib" path="libs/httpcore-${project.apache-httpcore-4.version}.jar" sourcepath="libs-sources/httpcore-${project.apache-httpcore-4.version}-sources.jar"/>
 	<classpathentry kind="lib" path="libs/jackson-core-asl-${project.jackson-core-asl.version}.jar" sourcepath="libs-sources/jackson-core-asl-${project.jackson-core-asl.version}-sources.jar"/>
 	<classpathentry kind="lib" path="libs/jackson-core-${project.jackson-core2.version}.jar" sourcepath="libs-sources/jackson-core-${project.jackson-core2.version}-sources.jar"/>
 	<classpathentry kind="lib" path="libs/jsr305-${project.jsr305.version}.jar"/>
diff --git a/google-http-client-assembly/readme.html b/google-http-client-assembly/readme.html
index 5e7af564..8a146df1 100644
--- a/google-http-client-assembly/readme.html
+++ b/google-http-client-assembly/readme.html
@@ -135,8 +135,8 @@
   required for general purpose Java applications :
   <ul>
     <li>commons-logging-${project.commons-logging.version}.jar</li>
-    <li>httpclient-${project.httpclient.version}.jar</li>
-    <li>httpcore-${project.httpcore.version}.jar</li>
+    <li>httpclient-${project.apache-httpclient-4.version}.jar</li>
+    <li>httpcore-${project.apache-httpcore-4.version}.jar</li>
   </ul>
 
 </body>
diff --git a/pom.xml b/pom.xml
index 5760c3e6..b7973cc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,7 @@
     <module>google-http-client-appengine</module>
     <module>google-http-client-android</module>
     <module>google-http-client-apache-v2</module>
+    <module>google-http-client-apache-v5</module>
     <module>google-http-client-protobuf</module>
     <module>google-http-client-gson</module>
     <module>google-http-client-jackson2</module>
@@ -92,7 +93,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>native-image-shared-config</artifactId>
-    <version>1.9.0</version>
+    <version>1.11.0</version>
   </parent>
 
   <!--
@@ -156,12 +157,22 @@
       <dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpclient</artifactId>
-        <version>${project.httpclient.version}</version>
+        <version>${project.apache-httpclient-4.version}</version>
       </dependency>
       <dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpcore</artifactId>
-        <version>${project.httpcore.version}</version>
+        <version>${project.apache-httpcore-4.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.httpcomponents.client5</groupId>
+        <artifactId>httpclient5</artifactId>
+        <version>${project.apache-httpclient-5.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.httpcomponents.core5</groupId>
+        <artifactId>httpcore5</artifactId>
+        <version>${project.apache-httpcore-5.version}</version>
       </dependency>
       <dependency>
         <groupId>com.google.guava</groupId>
@@ -236,7 +247,7 @@
       <dependency>
         <groupId>com.google.j2objc</groupId>
         <artifactId>j2objc-annotations</artifactId>
-        <version>2.8</version>
+        <version>3.0.0</version>
       </dependency>
       <dependency>
         <groupId>io.grpc</groupId>
@@ -361,7 +372,7 @@
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>animal-sniffer-maven-plugin</artifactId>
-          <version>1.23</version>
+          <version>1.24</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -597,12 +608,14 @@
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.jsr305.version>3.0.2</project.jsr305.version>
     <project.gson.version>2.10.1</project.gson.version>
-    <project.jackson-core2.version>2.14.2</project.jackson-core2.version>
+    <project.jackson-core2.version>2.17.2</project.jackson-core2.version>
     <project.protobuf-java.version>3.21.12</project.protobuf-java.version>
     <project.guava.version>30.1.1-android</project.guava.version>
     <project.xpp3.version>1.1.4c</project.xpp3.version>
-    <project.httpclient.version>4.5.14</project.httpclient.version>
-    <project.httpcore.version>4.4.16</project.httpcore.version>
+    <project.apache-httpclient-4.version>4.5.14</project.apache-httpclient-4.version>
+    <project.apache-httpcore-4.version>4.4.16</project.apache-httpcore-4.version>
+    <project.apache-httpclient-5.version>5.3.1</project.apache-httpclient-5.version>
+    <project.apache-httpcore-5.version>5.2.4</project.apache-httpcore-5.version>
     <project.opencensus.version>0.31.1</project.opencensus.version>
     <project.root-directory>..</project.root-directory>
     <project.surefire.version>3.2.5</project.surefire.version>
diff --git a/versions.txt b/versions.txt
index 963efeb8..87dbbb66 100644
--- a/versions.txt
+++ b/versions.txt
@@ -7,6 +7,7 @@ google-http-client-parent:1.44.2:1.44.3-SNAPSHOT
 google-http-client-android:1.44.2:1.44.3-SNAPSHOT
 google-http-client-android-test:1.44.2:1.44.3-SNAPSHOT
 google-http-client-apache-v2:1.44.2:1.44.3-SNAPSHOT
+google-http-client-apache-v5:1.44.2:1.44.3-SNAPSHOT
 google-http-client-appengine:1.44.2:1.44.3-SNAPSHOT
 google-http-client-assembly:1.44.2:1.44.3-SNAPSHOT
 google-http-client-findbugs:1.44.2:1.44.3-SNAPSHOT


Test patch:
diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpRequestTest.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpRequestTest.java
new file mode 100644
index 00000000..3b7ca4a2
--- /dev/null
+++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpRequestTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.api.client.http.apache.v5;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.api.client.http.ByteArrayContent;
+import com.google.api.client.http.HttpContent;
+import com.google.api.client.http.InputStreamContent;
+import com.google.api.client.http.LowLevelHttpResponse;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.junit.Test;
+
+public class Apache5HttpRequestTest {
+  @Test
+  public void testContentLengthSet() throws Exception {
+    HttpUriRequestBase base = new HttpPost("http://www.google.com");
+    Apache5HttpRequest request =
+        new Apache5HttpRequest(
+            new MockHttpClient() {
+              @Override
+              public ClassicHttpResponse executeOpen(
+                  HttpHost target, ClassicHttpRequest request, HttpContext context) {
+                return new MockClassicHttpResponse();
+              }
+            },
+            base);
+    HttpContent content =
+        new ByteArrayContent("text/plain", "sample".getBytes(StandardCharsets.UTF_8));
+    request.setStreamingContent(content);
+    request.setContentLength(content.getLength());
+    request.execute();
+
+    assertFalse(base.getEntity().isChunked());
+    assertEquals(6, base.getEntity().getContentLength());
+  }
+
+  @Test
+  public void testChunked() throws Exception {
+    byte[] buf = new byte[300];
+    Arrays.fill(buf, (byte) ' ');
+    HttpUriRequestBase base = new HttpPost("http://www.google.com");
+    Apache5HttpRequest request =
+        new Apache5HttpRequest(
+            new MockHttpClient() {
+              @Override
+              public ClassicHttpResponse executeOpen(
+                  HttpHost target, ClassicHttpRequest request, HttpContext context) {
+                return new MockClassicHttpResponse();
+              }
+            },
+            base);
+    HttpContent content = new InputStreamContent("text/plain", new ByteArrayInputStream(buf));
+    request.setStreamingContent(content);
+    request.execute();
+
+    assertTrue(base.getEntity().isChunked());
+    assertEquals(-1, base.getEntity().getContentLength());
+  }
+
+  @Test
+  public void testExecute_closeContent_closesResponse() throws Exception {
+    HttpUriRequestBase base = new HttpPost("http://www.google.com");
+    final InputStream responseContentStream = new ByteArrayInputStream(new byte[] {1, 2, 3});
+    BasicHttpEntity testEntity =
+        new BasicHttpEntity(responseContentStream, ContentType.DEFAULT_BINARY);
+    AtomicInteger closedResponseCounter = new AtomicInteger(0);
+    ClassicHttpResponse classicResponse =
+        new MockClassicHttpResponse() {
+          @Override
+          public HttpEntity getEntity() {
+            return testEntity;
+          }
+
+          @Override
+          public void close() {
+            closedResponseCounter.incrementAndGet();
+          }
+        };
+
+    Apache5HttpRequest request =
+        new Apache5HttpRequest(
+            new MockHttpClient() {
+              @Override
+              public ClassicHttpResponse executeOpen(
+                  HttpHost target, ClassicHttpRequest request, HttpContext context) {
+                return classicResponse;
+              }
+            },
+            base);
+    LowLevelHttpResponse response = request.execute();
+    assertTrue(response instanceof Apache5HttpResponse);
+
+    // we confirm that the classic response we prepared in this test is the same as the content's
+    // response
+    assertTrue(response.getContent() instanceof Apache5ResponseContent);
+    assertEquals(classicResponse, ((Apache5ResponseContent) response.getContent()).getResponse());
+
+    // we close the response's content stream and confirm the response is also closed
+    assertEquals(0, closedResponseCounter.get());
+    response.getContent().close();
+    assertEquals(1, closedResponseCounter.get());
+  }
+}
diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpTransportTest.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpTransportTest.java
new file mode 100644
index 00000000..99045d99
--- /dev/null
+++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/Apache5HttpTransportTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.api.client.http.apache.v5;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.LowLevelHttpResponse;
+import com.google.api.client.util.ByteArrayStreamingContent;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.hc.client5.http.ConnectTimeoutException;
+import org.apache.hc.client5.http.HttpHostConnectException;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpRequestMapper;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
+import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
+import org.apache.hc.core5.http.impl.io.HttpService;
+import org.apache.hc.core5.http.io.HttpClientConnection;
+import org.apache.hc.core5.http.io.HttpRequestHandler;
+import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
+import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.junit.Assert;
+import org.junit.Test;
+
+/** Tests {@link Apache5HttpTransport}. */
+public class Apache5HttpTransportTest {
+
+  @Test
+  public void testApacheHttpTransport() {
+    Apache5HttpTransport transport = new Apache5HttpTransport();
+    checkHttpTransport(transport);
+    assertFalse(transport.isMtls());
+  }
+
+  @Test
+  public void testApacheHttpTransportWithParam() {
+    Apache5HttpTransport transport = new Apache5HttpTransport(HttpClients.custom().build(), true);
+    checkHttpTransport(transport);
+    assertTrue(transport.isMtls());
+  }
+
+  @Test
+  public void testNewDefaultHttpClient() {
+    HttpClient client = Apache5HttpTransport.newDefaultHttpClient();
+    checkHttpClient(client);
+  }
+
+  private void checkHttpTransport(Apache5HttpTransport transport) {
+    assertNotNull(transport);
+    HttpClient client = transport.getHttpClient();
+    checkHttpClient(client);
+  }
+
+  private void checkHttpClient(HttpClient client) {
+    assertNotNull(client);
+    // TODO(chingor): Is it possible to test this effectively? The newer HttpClient implementations
+    // are read-only and we're testing that we built the client with the right configuration
+  }
+
+  @Test
+  public void testRequestsWithContent() throws IOException {
+    // This test confirms that we can set the content on any type of request
+    HttpClient mockClient =
+        new MockHttpClient() {
+          @Override
+          public ClassicHttpResponse executeOpen(
+              HttpHost target, ClassicHttpRequest request, HttpContext context) {
+            return new MockClassicHttpResponse();
+          }
+        };
+    Apache5HttpTransport transport = new Apache5HttpTransport(mockClient);
+
+    // Test GET.
+    execute(transport.buildRequest("GET", "http://www.test.url"));
+    // Test DELETE.
+    execute(transport.buildRequest("DELETE", "http://www.test.url"));
+    // Test HEAD.
+    execute(transport.buildRequest("HEAD", "http://www.test.url"));
+
+    // Test PATCH.
+    execute(transport.buildRequest("PATCH", "http://www.test.url"));
+    // Test PUT.
+    execute(transport.buildRequest("PUT", "http://www.test.url"));
+    // Test POST.
+    execute(transport.buildRequest("POST", "http://www.test.url"));
+    // Test PATCH.
+    execute(transport.buildRequest("PATCH", "http://www.test.url"));
+  }
+
+  private void execute(Apache5HttpRequest request) throws IOException {
+    byte[] bytes = "abc".getBytes(StandardCharsets.UTF_8);
+    request.setStreamingContent(new ByteArrayStreamingContent(bytes));
+    request.setContentType("text/html");
+    request.setContentLength(bytes.length);
+    request.execute();
+  }
+
+  @Test
+  public void testRequestShouldNotFollowRedirects() throws IOException {
+    final AtomicInteger requestsAttempted = new AtomicInteger(0);
+    HttpRequestExecutor requestExecutor =
+        new HttpRequestExecutor() {
+          @Override
+          public ClassicHttpResponse execute(
+              ClassicHttpRequest request, HttpClientConnection connection, HttpContext context)
+              throws IOException, HttpException {
+            ClassicHttpResponse response = new MockClassicHttpResponse();
+            response.setCode(302);
+            response.setReasonPhrase(null);
+            response.addHeader("location", "https://google.com/path");
+            response.addHeader(HttpHeaders.SET_COOKIE, "");
+            requestsAttempted.incrementAndGet();
+            return response;
+          }
+        };
+    HttpClient client = HttpClients.custom().setRequestExecutor(requestExecutor).build();
+    Apache5HttpTransport transport = new Apache5HttpTransport(client);
+    Apache5HttpRequest request = transport.buildRequest("GET", "https://google.com");
+    LowLevelHttpResponse response = request.execute();
+    assertEquals(1, requestsAttempted.get());
+    assertEquals(302, response.getStatusCode());
+  }
+
+  @Test
+  public void testRequestCanSetHeaders() {
+    final AtomicBoolean interceptorCalled = new AtomicBoolean(false);
+    HttpClient client =
+        HttpClients.custom()
+            .addRequestInterceptorFirst(
+                new HttpRequestInterceptor() {
+                  @Override
+                  public void process(
+                      HttpRequest request, EntityDetails details, HttpContext context)
+                      throws HttpException, IOException {
+                    Header header = request.getFirstHeader("foo");
+                    assertNotNull("Should have found header", header);
+                    assertEquals("bar", header.getValue());
+                    interceptorCalled.set(true);
+                    throw new IOException("cancelling request");
+                  }
+                })
+            .build();
+
+    Apache5HttpTransport transport = new Apache5HttpTransport(client);
+    Apache5HttpRequest request = transport.buildRequest("GET", "https://google.com");
+    request.addHeader("foo", "bar");
+    try {
+      LowLevelHttpResponse response = request.execute();
+      fail("should not actually make the request");
+    } catch (IOException exception) {
+      assertEquals("cancelling request", exception.getMessage());
+    }
+    assertTrue("Expected to have called our test interceptor", interceptorCalled.get());
+  }
+
+  @Test(timeout = 10_000L)
+  public void testConnectTimeout() {
+    // TODO(chanseok): Java 17 returns an IOException (SocketException: Network is unreachable).
+    // Figure out a way to verify connection timeout works on Java 17+.
+    assumeTrue(System.getProperty("java.version").compareTo("17") < 0);
+
+    HttpTransport httpTransport = new Apache5HttpTransport();
+    GenericUrl url = new GenericUrl("http://google.com:81");
+    try {
+      httpTransport.createRequestFactory().buildGetRequest(url).setConnectTimeout(100).execute();
+      fail("should have thrown an exception");
+    } catch (HttpHostConnectException | ConnectTimeoutException expected) {
+      // expected
+    } catch (IOException e) {
+      fail("unexpected IOException: " + e.getClass().getName() + ": " + e.getMessage());
+    }
+  }
+
+  private static class FakeServer implements AutoCloseable {
+    private final HttpServer server;
+
+    FakeServer(final HttpRequestHandler httpHandler) throws IOException {
+      HttpRequestMapper<HttpRequestHandler> mapper =
+          new HttpRequestMapper<HttpRequestHandler>() {
+            @Override
+            public HttpRequestHandler resolve(HttpRequest request, HttpContext context)
+                throws HttpException {
+              return httpHandler;
+            };
+          };
+      server =
+          new HttpServer(
+              0,
+              HttpService.builder()
+                  .withHttpProcessor(
+                      new HttpProcessor() {
+                        @Override
+                        public void process(
+                            HttpRequest request, EntityDetails entity, HttpContext context)
+                            throws HttpException, IOException {}
+
+                        @Override
+                        public void process(
+                            HttpResponse response, EntityDetails entity, HttpContext context)
+                            throws HttpException, IOException {}
+                      })
+                  .withHttpServerRequestHandler(new BasicHttpServerRequestHandler(mapper))
+                  .build(),
+              null,
+              null,
+              null,
+              null,
+              null,
+              null);
+      //       server.createContext("/", httpHandler);
+      server.start();
+    }
+
+    public int getPort() {
+      return server.getLocalPort();
+    }
+
+    @Override
+    public void close() {
+      server.initiateShutdown();
+    }
+  }
+
+  @Test
+  public void testNormalizedUrl() throws IOException {
+    final HttpRequestHandler handler =
+        new HttpRequestHandler() {
+          @Override
+          public void handle(
+              ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context)
+              throws HttpException, IOException {
+            // Extract the request URI and convert to bytes
+            byte[] responseData = request.getRequestUri().getBytes(StandardCharsets.UTF_8);
+
+            // Set the response headers (status code and content length)
+            response.setCode(HttpStatus.SC_OK);
+            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length));
+
+            // Set the response entity (body)
+            ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN);
+            response.setEntity(entity);
+          }
+        };
+    try (FakeServer server = new FakeServer(handler)) {
+      HttpTransport transport = new Apache5HttpTransport();
+      GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
+      testUrl.setPort(server.getPort());
+      com.google.api.client.http.HttpResponse response =
+          transport.createRequestFactory().buildGetRequest(testUrl).execute();
+      assertEquals(200, response.getStatusCode());
+      assertEquals("/foo//bar", response.parseAsString());
+    }
+  }
+
+  @Test
+  public void testReadErrorStream() throws IOException {
+    final HttpRequestHandler handler =
+        new HttpRequestHandler() {
+          @Override
+          public void handle(
+              ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context)
+              throws HttpException, IOException {
+            byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8);
+            response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden
+            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length));
+            ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN);
+            response.setEntity(entity);
+          }
+        };
+    try (FakeServer server = new FakeServer(handler)) {
+      HttpTransport transport = new Apache5HttpTransport();
+      GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
+      testUrl.setPort(server.getPort());
+      com.google.api.client.http.HttpRequest getRequest =
+          transport.createRequestFactory().buildGetRequest(testUrl);
+      getRequest.setThrowExceptionOnExecuteError(false);
+      com.google.api.client.http.HttpResponse response = getRequest.execute();
+      assertEquals(403, response.getStatusCode());
+      assertEquals("Forbidden", response.parseAsString());
+    }
+  }
+
+  @Test
+  public void testReadErrorStream_withException() throws IOException {
+    final HttpRequestHandler handler =
+        new HttpRequestHandler() {
+          @Override
+          public void handle(
+              ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context)
+              throws HttpException, IOException {
+            byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8);
+            response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden
+            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length));
+            ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN);
+            response.setEntity(entity);
+          }
+        };
+    try (FakeServer server = new FakeServer(handler)) {
+      HttpTransport transport = new Apache5HttpTransport();
+      GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar");
+      testUrl.setPort(server.getPort());
+      com.google.api.client.http.HttpRequest getRequest =
+          transport.createRequestFactory().buildGetRequest(testUrl);
+      try {
+        getRequest.execute();
+        Assert.fail();
+      } catch (HttpResponseException ex) {
+        assertEquals("Forbidden", ex.getContent());
+      }
+    }
+  }
+
+  private boolean isWindows() {
+    return System.getProperty("os.name").startsWith("Windows");
+  }
+}
diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockClassicHttpResponse.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockClassicHttpResponse.java
new file mode 100644
index 00000000..09172174
--- /dev/null
+++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockClassicHttpResponse.java
@@ -0,0 +1,182 @@
+package com.google.api.client.http.apache.v5;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.ProtocolVersion;
+
+public class MockClassicHttpResponse implements ClassicHttpResponse {
+  List<Header> headers = new ArrayList<>();
+  int code = 200;
+
+  @Override
+  public int getCode() {
+    return code;
+  }
+
+  @Override
+  public void setCode(int code) {
+    this.code = code;
+  }
+
+  @Override
+  public String getReasonPhrase() {
+    return null;
+  }
+
+  @Override
+  public void setReasonPhrase(String reason) {}
+
+  @Override
+  public Locale getLocale() {
+    return null;
+  }
+
+  @Override
+  public void setLocale(Locale loc) {}
+
+  @Override
+  public void setVersion(ProtocolVersion version) {}
+
+  @Override
+  public ProtocolVersion getVersion() {
+    return HttpVersion.HTTP_1_1;
+  }
+
+  @Override
+  public void addHeader(Header header) {
+    headers.add(header);
+  }
+
+  @Override
+  public void addHeader(String name, Object value) {
+    addHeader(newHeader(name, value));
+  }
+
+  private Header newHeader(String key, Object value) {
+    return new Header() {
+      @Override
+      public boolean isSensitive() {
+        return false;
+      }
+
+      @Override
+      public String getName() {
+        return key;
+      }
+
+      @Override
+      public String getValue() {
+        return value.toString();
+      }
+    };
+  }
+
+  @Override
+  public void setHeader(Header header) {
+    if (headers.contains(header)) {
+      int index = headers.indexOf(header);
+      headers.set(index, header);
+    } else {
+      addHeader(header);
+    }
+  }
+
+  @Override
+  public void setHeader(String name, Object value) {
+    setHeader(newHeader(name, value));
+  }
+
+  @Override
+  public void setHeaders(Header... headers) {
+    for (Header header : headers) {
+      setHeader(header);
+    }
+  }
+
+  @Override
+  public boolean removeHeader(Header header) {
+    if (headers.contains(header)) {
+      headers.remove(headers.indexOf(header));
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public boolean removeHeaders(String name) {
+    int initialSize = headers.size();
+    for (Header header :
+        headers.stream().filter(h -> h.getName() == name).collect(Collectors.toList())) {
+      removeHeader(header);
+    }
+    return headers.size() < initialSize;
+  }
+
+  @Override
+  public boolean containsHeader(String name) {
+    return headers.stream().anyMatch(h -> h.getName() == name);
+  }
+
+  @Override
+  public int countHeaders(String name) {
+    return headers.size();
+  }
+
+  @Override
+  public Header getFirstHeader(String name) {
+    return headers.stream().findFirst().orElse(null);
+  }
+
+  @Override
+  public Header getHeader(String name) throws ProtocolException {
+    return headers.stream().filter(h -> h.getName() == name).findFirst().orElse(null);
+  }
+
+  @Override
+  public Header[] getHeaders() {
+    return headers.toArray(new Header[0]);
+  }
+
+  @Override
+  public Header[] getHeaders(String name) {
+    return headers.stream()
+        .filter(h -> h.getName() == name)
+        .collect(Collectors.toList())
+        .toArray(new Header[0]);
+  }
+
+  @Override
+  public Header getLastHeader(String name) {
+    return headers.isEmpty() ? null : headers.get(headers.size() - 1);
+  }
+
+  @Override
+  public Iterator<Header> headerIterator() {
+    return headers.iterator();
+  }
+
+  @Override
+  public Iterator<Header> headerIterator(String name) {
+    return headers.stream().filter(h -> h.getName() == name).iterator();
+  }
+
+  @Override
+  public void close() throws IOException {}
+
+  @Override
+  public HttpEntity getEntity() {
+    return null;
+  }
+
+  @Override
+  public void setEntity(HttpEntity entity) {}
+}
diff --git a/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockHttpClient.java b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockHttpClient.java
new file mode 100644
index 00000000..8d26096c
--- /dev/null
+++ b/google-http-client-apache-v5/src/test/java/com/google/api/client/http/apache/v5/MockHttpClient.java
@@ -0,0 +1,86 @@
+package com.google.api.client.http.apache.v5;
+
+import com.google.api.client.util.Preconditions;
+import java.io.IOException;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+public class MockHttpClient implements HttpClient {
+
+  /** HTTP response code to use. */
+  int responseCode;
+
+  /** Returns the HTTP response code to use. */
+  public final int getResponseCode() {
+    return responseCode;
+  }
+
+  /** Sets the HTTP response code to use. */
+  public MockHttpClient setResponseCode(int responseCode) {
+    Preconditions.checkArgument(responseCode >= 0);
+    this.responseCode = responseCode;
+    return this;
+  }
+
+  @Override
+  public HttpResponse execute(ClassicHttpRequest request) throws IOException {
+    return null;
+  }
+
+  @Override
+  public HttpResponse execute(ClassicHttpRequest request, HttpContext context) throws IOException {
+    return null;
+  }
+
+  @Override
+  public ClassicHttpResponse execute(HttpHost target, ClassicHttpRequest request)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public HttpResponse execute(HttpHost target, ClassicHttpRequest request, HttpContext context)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public <T> T execute(
+      ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public <T> T execute(
+      ClassicHttpRequest request,
+      HttpContext context,
+      HttpClientResponseHandler<? extends T> responseHandler)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public <T> T execute(
+      HttpHost target,
+      ClassicHttpRequest request,
+      HttpClientResponseHandler<? extends T> responseHandler)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public <T> T execute(
+      HttpHost target,
+      ClassicHttpRequest request,
+      HttpContext context,
+      HttpClientResponseHandler<? extends T> responseHandler)
+      throws IOException {
+    return null;
+  }
+}