diff --git a/.cursor/rules/cloudinary.mdc b/.cursor/rules/cloudinary.mdc
new file mode 100644
index 00000000..d1314e20
--- /dev/null
+++ b/.cursor/rules/cloudinary.mdc
@@ -0,0 +1,8 @@
+---
+description: Cloudinary cloudinary_java — agent guide
+alwaysApply: true
+---
+
+Read and follow `AGENTS.md` in the repository root. It is the single
+authoritative guide for this package: build/test commands, conventions,
+gotchas, and when to use this SDK versus a sibling Cloudinary package.
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 00000000..c1adc027
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,5 @@
+# Cloudinary cloudinary_java — instructions for AI coding agents
+
+Read `AGENTS.md` in the repository root and follow it. It is the single
+authoritative guide for this package: build/test commands, conventions,
+gotchas, and when to use this SDK versus a sibling Cloudinary package.
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..41d90039
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,81 @@
+# AGENTS.md — cloudinary_java
+
+## What this package is (one line)
+Official Cloudinary server-side SDK for the JVM: upload assets, build transformation/delivery URLs, and call the Admin API from Java backends. The published artifact is `cloudinary-http5` (group `com.cloudinary`), built on Apache HttpClient 5.
+
+## When to use this / when NOT to use this
+- **Use this when:** your code runs on a **server or in the JVM** (Spring Boot, servlets, batch jobs, CLI tools) and needs signed uploads, signed delivery URLs, server-side URL/tag generation, or Admin API calls — i.e. anywhere the `api_secret` must stay private.
+- **Do NOT use this when:** the code runs on an **Android device** (use [`cloudinary_android`](https://github.com/cloudinary/cloudinary_android) — it doesn't expect the secret on-device); or you want an idiomatic, coroutine-friendly **Kotlin** client (use [`cloudinary_kotlin`](https://github.com/cloudinary/cloudinary_kotlin)); or you need the no-code/agent path (use the Cloudinary MCP server).
+- **Sibling packages / modules in this repo:** `cloudinary-core` (provider-agnostic core: URL/transformation builders, signing, params — no HTTP) → `cloudinary-http5` (HTTP transport on HttpClient 5, the artifact you depend on) → `cloudinary-taglib` (JSP tags). `cloudinary-test-common` holds shared test code only. The legacy `cloudinary-http45` artifact (HttpClient 4.5) belongs to the **1.x line only** — do not use it for 2.x.
+
+## Setup
+**Maven** (`pom.xml`):
+```xml
+
+ com.cloudinary
+ cloudinary-http5
+ 2.4.0
+
+```
+**Gradle** (`build.gradle`):
+```groovy
+implementation 'com.cloudinary:cloudinary-http5:2.4.0'
+```
+
+Required configuration / credentials — set via env var (or system property), constructor, or per-call config:
+```bash
+export CLOUDINARY_URL=cloudinary://:@
+```
+
+## Minimal runnable example
+```java
+import com.cloudinary.Cloudinary;
+import com.cloudinary.Transformation;
+import com.cloudinary.utils.ObjectUtils;
+
+Cloudinary cloudinary = new Cloudinary(); // reads CLOUDINARY_URL
+
+// Upload a local file (needs api_key + api_secret)
+cloudinary.uploader().upload("my_picture.jpg", ObjectUtils.emptyMap());
+
+// Build a transformation/delivery URL (cloud_name only)
+String url = cloudinary.url()
+ .transformation(new Transformation().width(100).height(150).crop("fill"))
+ .generate("sample.jpg");
+```
+
+## Build / test commands (run these after editing)
+This is a **multi-module Gradle** build; use the wrapper (`./gradlew`, or `gradlew.bat` on Windows). Requires **JDK 8+** (`sourceCompatibility`/`targetCompatibility = 1.8`).
+
+```bash
+./gradlew build # compile + assemble all modules
+./gradlew :cloudinary-core:test # unit tests for the core module
+./gradlew :cloudinary-http5:test # tests for the HTTP module
+```
+Integration tests hit a live Cloudinary cloud and need credentials. CI runs them per module via the `ciTest` task (excludes the `TimeoutTest` category) with `CLOUDINARY_URL` passed as a system property:
+```bash
+./gradlew -DCLOUDINARY_URL=$CLOUDINARY_URL ciTest -p cloudinary-http5 -i
+```
+The CI workflow first runs `./gradlew createTestSubAccount -PmoduleName=` to provision a throwaway test cloud (writes `tools/cloudinary_url.txt`); skip this for offline/unit work. There is no separate lint or formatter task in the Gradle build.
+
+## Conventions & gotchas
+- **Layered modules:** put provider-agnostic logic (URL building, signing, param handling) in `cloudinary-core`; only HTTP transport concerns belong in `cloudinary-http5`. Don't push core logic into the HTTP module.
+- **Java 8 source level** — no Java 9+ language features or APIs in any module.
+- **Secrets stay server-side:** signed uploads, signed delivery URLs, and Admin API calls require `api_secret`. Never ship it to a browser or Android bundle — that's the entire reason this server SDK exists.
+- **Artifact coordinate:** the current artifact is `cloudinary-http5`. `cloudinary-http45` is the legacy HttpClient-4.5 coordinate for 1.x — update the coordinate when upgrading from 1.x.
+- **Version support:** 2.0.0+ requires Java 8+; 1.1.0–1.39.0 supported Java 6+.
+
+## Canonical docs (leave the repo for depth)
+- Java SDK guide: https://cloudinary.com/documentation/java_integration
+- Image/video manipulation: https://cloudinary.com/documentation/java_image_manipulation
+- Transformation & API reference: https://cloudinary.com/documentation/cloudinary_references
+- Maven Central artifact: https://central.sonatype.com/artifact/com.cloudinary/cloudinary-http5
+- MCP server (agent/no-code path): https://github.com/cloudinary/mcp-servers
+
+## Agent / MCP note
+If the capability you need is also exposed via the Cloudinary MCP servers, prefer the MCP tool for autonomous task execution and use this SDK for code generation. See cloudinary/mcp-servers.
+
+## Commit / PR conventions
+- Open issues/PRs against https://github.com/cloudinary/cloudinary_java. (There is no `CONTRIBUTING.md` in this repo.)
+- All matrix CI jobs (modules `core`, `http5`, `taglib` on JDK 8) must pass before merge.
+- Add a `CHANGELOG.md` entry for user-facing changes.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..6c294bfe
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,33 @@
+@AGENTS.md
+
+# CLAUDE.md — cloudinary_java
+
+## Claude Code-specific notes
+
+**Primary reference:** `AGENTS.md` (imported above) covers setup, build commands, conventions, and gotchas. Read it before touching any file.
+
+## What this repo is
+
+`cloudinary_java` is the official server-side Cloudinary SDK for the JVM. The published artifact is `cloudinary-http5` (group `com.cloudinary`), built on Apache HttpClient 5. Use it from Java backends — Spring Boot, servlets, batch jobs — where the `api_secret` must stay private.
+
+## Key constraints / gotchas
+
+- **Multi-module Gradle build.** Depend on `cloudinary-http5`; it pulls in `cloudinary-core` transitively. Do not depend on `cloudinary-core` directly for HTTP work.
+- **Artifact coordinate changed at 2.x.** The legacy `cloudinary-http45` coordinate belongs to the 1.x line only — there is no `cloudinary-http45:2.x`. Change the `artifactId` to `cloudinary-http5` when upgrading from 1.x.
+- **Java 8 source level.** No Java 9+ language features or APIs anywhere in the codebase (`sourceCompatibility = targetCompatibility = 1.8`).
+- **`api_secret` is server-only.** Never ship it to a browser or Android bundle. Use the signed-upload pattern (server signs, browser posts directly to Cloudinary) to keep the secret off the client.
+- **Not for Android.** Use [`cloudinary_android`](https://github.com/cloudinary/cloudinary_android). Not for idiomatic Kotlin: use [`cloudinary_kotlin`](https://github.com/cloudinary/cloudinary_kotlin).
+- **Integration tests need a live cloud.** CI provisions a throwaway sub-account via `./gradlew createTestSubAccount`. For offline/unit work, skip that step — unit tests in `cloudinary-core` and `cloudinary-http5` run without credentials.
+
+## Verified build commands
+
+```bash
+./gradlew build # compile + assemble all modules
+./gradlew :cloudinary-core:test # unit tests, no credentials needed
+./gradlew :cloudinary-http5:test # HTTP module unit tests
+
+# Full integration suite (needs CLOUDINARY_URL):
+./gradlew -DCLOUDINARY_URL=$CLOUDINARY_URL ciTest -p cloudinary-http5 -i
+```
+
+Use `gradlew.bat` instead of `./gradlew` on Windows. There is no separate lint or formatter task in the Gradle build.
diff --git a/README.md b/README.md
index 1b28b43b..e0b24df5 100644
--- a/README.md
+++ b/README.md
@@ -1,148 +1,135 @@
-[](https://travis-ci.org/cloudinary/cloudinary_java)
+# Cloudinary Java SDK
-Cloudinary
-==========
+[](https://central.sonatype.com/artifact/com.cloudinary/cloudinary-http5)
+[](./LICENSE)
+[](https://github.com/cloudinary/cloudinary_java/actions/workflows/build.yml)
-## About
-The Cloudinary Java SDK allows you to quickly and easily integrate your application with Cloudinary.
-Effortlessly optimize and transform your cloud's assets.
+The server-side Cloudinary SDK for the JVM. Use it from a Java server or build step to upload assets, build transformation and delivery URLs, and call the Admin API. The published artifact is `cloudinary-http5` (group `com.cloudinary`), built on Apache HttpClient 5. The current release line (2.x) requires Java 8 or later.
-### Additional documentation
-This Readme provides basic installation and usage information.
-For the complete documentation, see the [Java SDK Guide](https://cloudinary.com/documentation/java_integration).
-
-## Table of Contents
-- [Key Features](#key-features)
-- [Version Support](#Version-Support)
-- [Installation](#installation)
-- [Usage](#usage)
- - [Setup](#Setup)
- - [Transform and Optimize Assets](#Transform-and-Optimize-Assets)
- - [File upload](#File-upload)
-
-## Key Features
-- [Transform](https://cloudinary.com/documentation/java_video_manipulation) and [optimize](https://cloudinary.com/documentation/java_image_manipulation#image_optimizations) assets (links to docs).
-- [Upload assets to cloud](https://cloudinary.com/documentation/java_image_and_video_upload)
-
-## Version Support
-| SDK Version | Java 6+ | Java 8 |
-|----------------|---------|--------|
-| 1.1.0 - 1.39.0 | V | |
-| 2.0.0+ | | V |
+## Installation
-
+This is a multi-module library. Depend on `cloudinary-http5`, which pulls in `cloudinary-core` transitively. The latest version on Maven Central is `2.4.0`. On the 1.x line the artifact was `cloudinary-http45` (Apache HttpClient 4.5); there is no `cloudinary-http45` 2.x release, so change the coordinate to `cloudinary-http5` when upgrading.
-## Installation
-The cloudinary_java library is available in [Maven Central](https://mvnrepository.com/artifact/com.cloudinary/cloudinary-core). To use it, add the following dependency to your pom.xml :
+Maven — add to `pom.xml`:
```xml
com.cloudinary
- cloudinary-http45
- 2.3.1
+ cloudinary-http5
+ 2.4.0
```
-## Usage
-### Setup
+Gradle — add to `build.gradle`:
-Each request for building a URL of a remote cloud resource must have the `cloud_name` parameter set.
-Each request to our secure APIs (e.g., image uploads, eager sprite generation) must have the `api_key` and `api_secret` parameters set.
-See [API, URLs and access identifiers](https://cloudinary.com/documentation/solution_overview#account_and_api_setup) for more details.
+```groovy
+implementation 'com.cloudinary:cloudinary-http5:2.4.0'
+```
-Setting the `cloud_name`, `api_key` and `api_secret` parameters can be done either directly in each call to a Cloudinary method,
-by when initializing the Cloudinary object, or by using the CLOUDINARY_URL environment variable / system property.
+## Configuration
+
+The entry point is the `Cloudinary` object. The no-arg constructor reads credentials from the `CLOUDINARY_URL` environment variable (or a system property of the same name):
+
+```bash
+export CLOUDINARY_URL=cloudinary://:@
+```
-The entry point of the library is the Cloudinary object.
```java
+import com.cloudinary.Cloudinary;
+
+// Reads CLOUDINARY_URL from the environment / system properties.
Cloudinary cloudinary = new Cloudinary();
```
-Here's an example of setting the configuration parameters programatically:
+To set the credentials in code instead, pass a config map:
```java
-Map config = new HashMap();
-config.put("cloud_name", "n07t21i7");
-config.put("api_key", "123456789012345");
-config.put("api_secret", "abcdeghijklmnopqrstuvwxyz12");
-Cloudinary cloudinary = new Cloudinary(config);
+import com.cloudinary.Cloudinary;
+import com.cloudinary.utils.ObjectUtils;
+
+Cloudinary cloudinary = new Cloudinary(ObjectUtils.asMap(
+ "cloud_name", "my_cloud_name",
+ "api_key", "my_key",
+ "api_secret", "my_secret",
+ "secure", true
+));
```
-Another example of setting the configuration parameters by providing the CLOUDINARY_URL value to the constructor:
+Keep the API secret on the server. Don't put it in client-side code or commit it to version control. Building a delivery URL needs only `cloud_name`; anything that signs a request (uploads, the Admin API) also needs `api_key` and `api_secret`.
- Cloudinary cloudinary = new Cloudinary("cloudinary://123456789012345:abcdeghijklmnopqrstuvwxyz12@n07t21i7");
+## Quick examples
-### Transform and Optimize Assets
-- [See full documentation](https://cloudinary.com/documentation/java_image_manipulation)
-Any image uploaded to Cloudinary can be transformed and embedded using powerful view helper methods:
+### Upload a file
-The following example generates the url for accessing an uploaded `sample` image while transforming it to fill a 100x150 rectangle:
+`uploader().upload(file, options)` takes the file as its first argument — a local path `String`, a remote URL `String`, a `File`, or a `byte[]` — and a required options `Map`. Pass `ObjectUtils.emptyMap()` when you have no options. It returns a `Map` whose fields include `public_id` and `secure_url`:
```java
-cloudinary.url().transformation(new Transformation().width(100).height(150).crop("fill")).generate("sample.jpg");
-```
+import com.cloudinary.Cloudinary;
+import com.cloudinary.utils.ObjectUtils;
+import java.util.Map;
-Another example, emedding a smaller version of an uploaded image while generating a 90x90 face detection based thumbnail:
+Cloudinary cloudinary = new Cloudinary(); // reads CLOUDINARY_URL
-```java
-cloudinary.url().transformation(new Transformation().width(90).height(90).crop("thumb").gravity("face")).generate("woman.jpg");
+Map result = cloudinary.uploader().upload("my_picture.jpg", ObjectUtils.asMap(
+ "public_id", "cms/hero" // optional: where the asset lives in your media library
+));
+System.out.println(result.get("public_id") + " " + result.get("secure_url"));
```
-You can provide either a Facebook name or a numeric ID of a Facebook profile or a fan page.
+### Build and optimize a delivery URL
-Embedding a Facebook profile to match your graphic design is very simple:
+`cloudinary.url()` returns a `Url` builder; `.generate(source)` is synchronous and returns a `String` — no network call. This one resizes to a 100x150 fill crop and lets Cloudinary pick the format and quality for the requesting browser (`f_auto`, `q_auto`):
```java
-cloudinary.url().type("facebook").transformation(new Transformation().width(130).height(130).crop("fill").gravity("north_west")).generate("billclinton.jpg");
-```
-
-### File upload
-Assuming you have your Cloudinary configuration parameters defined (`cloud_name`, `api_key`, `api_secret`), uploading to Cloudinary is very simple.
+import com.cloudinary.Cloudinary;
+import com.cloudinary.Transformation;
-The following example uploads a local JPG to the cloud:
+Cloudinary cloudinary = new Cloudinary("cloudinary://:@demo");
-```java
-cloudinary.uploader().upload("my_picture.jpg", ObjectUtils.emptyMap());
+String url = cloudinary.url()
+ .transformation(new Transformation()
+ .width(100).height(150).crop("fill")
+ .fetchFormat("auto").quality("auto"))
+ .generate("sample.jpg");
+// https://res.cloudinary.com/demo/image/upload/c_fill,f_auto,h_150,q_auto,w_100/sample.jpg
```
-The uploaded image is assigned a randomly generated public ID. The image is immediately available for download through a CDN:
-
-```java
-cloudinary.url().generate("abcfrmo8zul1mafopawefg.jpg");
-
-# http://res.cloudinary.com/demo/image/upload/abcfrmo8zul1mafopawefg.jpg
-```
+### Retrieve asset details
-You can also specify your own public ID:
+`api().resource(publicId, options)` takes a public ID and a required options `Map`, and returns the asset's metadata, including its `format`, `width`, `height`, and `secure_url`:
```java
-cloudinary.uploader().upload("http://www.example.com/image.jpg", ObjectUtils.asMap("public_id", "sample_remote"));
+import com.cloudinary.Cloudinary;
+import com.cloudinary.api.ApiResponse;
+import com.cloudinary.utils.ObjectUtils;
-cloudinary.url().generate("sample_remote.jpg");
+Cloudinary cloudinary = new Cloudinary(); // reads CLOUDINARY_URL
-# http://res.cloudinary.com/demo/image/upload/sample_remote.jpg
+ApiResponse asset = cloudinary.api().resource("sample", ObjectUtils.emptyMap());
+System.out.println(asset.get("format") + " " + asset.get("width") + "x" + asset.get("height"));
+System.out.println(asset.get("secure_url"));
```
-## Contributions
-See [contributing guidelines](/CONTRIBUTING.md).
+## For AI agents
+
+`cloudinary-http5` (group `com.cloudinary`) is the Java/JVM server-side SDK. Choose it for backend upload, asset administration, and signed URL or tag generation, where the API secret stays private. For other targets, choose a different package:
+
+| Target | Package |
+|---|---|
+| Android app (secret not on-device) | [`cloudinary_android`](https://github.com/cloudinary/cloudinary_android) |
+| Idiomatic, coroutine-friendly Kotlin client | [`cloudinary_kotlin`](https://github.com/cloudinary/cloudinary_kotlin) |
+| Autonomous agent / no-code path | [Cloudinary MCP servers](https://github.com/cloudinary/mcp-servers) |
-## Get Help
-- [Open a Github issue](https://github.com/CloudinaryLtd/cloudinary_java/issues) (for issues related to the SDK)
-- [Open a support ticket](https://cloudinary.com/contact) (for issues related to your account)
+The 2.x artifact is `cloudinary-http5`. The legacy `cloudinary-http45` coordinate belongs to the 1.x line only and has no 2.x release.
-## About Cloudinary
-Cloudinary is a powerful media API for websites and mobile apps alike, Cloudinary enables developers to efficiently manage, transform, optimize, and deliver images and videos through multiple CDNs. Ultimately, viewers enjoy responsive and personalized visual-media experiences—irrespective of the viewing device.
+## Links
-## Additional Resources
-- [Cloudinary Transformation and REST API References](https://cloudinary.com/documentation/cloudinary_references): Comprehensive references, including syntax and examples for all SDKs.
-- [MediaJams.dev](https://mediajams.dev/): Bite-size use-case tutorials written by and for Cloudinary Developers
-- [DevJams](https://www.youtube.com/playlist?list=PL8dVGjLA2oMr09amgERARsZyrOz_sPvqw): Cloudinary developer podcasts on YouTube.
-- [Cloudinary Academy](https://training.cloudinary.com/): Free self-paced courses, instructor-led virtual courses, and on-site courses.
-- [Code Explorers and Feature Demos](https://cloudinary.com/documentation/code_explorers_demos_index): A one-stop shop for all code explorers, Postman collections, and feature demos found in the docs.
-- [Cloudinary Roadmap](https://cloudinary.com/roadmap): Your chance to follow, vote, or suggest what Cloudinary should develop next.
-- [Cloudinary Facebook Community](https://www.facebook.com/groups/CloudinaryCommunity): Learn from and offer help to other Cloudinary developers.
-- [Cloudinary Account Registration](https://cloudinary.com/users/register/free): Free Cloudinary account registration.
-- [Cloudinary Website](https://cloudinary.com)
+- [Java SDK guide](https://cloudinary.com/documentation/java_integration)
+- [Upload](https://cloudinary.com/documentation/java_image_and_video_upload)
+- [Asset administration (Admin API)](https://cloudinary.com/documentation/java_asset_administration)
+- [Image manipulation](https://cloudinary.com/documentation/java_image_manipulation)
+- [Transformation and API references](https://cloudinary.com/documentation/cloudinary_references)
+- [Documentation llms.txt index](https://cloudinary.com/documentation/llms.txt)
+- [Artifact on Maven Central](https://central.sonatype.com/artifact/com.cloudinary/cloudinary-http5)
-## Licence
Released under the MIT license.