Skip to content

feat: add Java SDK language support#1477

Open
TamsavYT wants to merge 22 commits intoappwrite:masterfrom
TamsavYT:feat/java-sdk
Open

feat: add Java SDK language support#1477
TamsavYT wants to merge 22 commits intoappwrite:masterfrom
TamsavYT:feat/java-sdk

Conversation

@TamsavYT
Copy link
Copy Markdown

What does this PR do?

Adds a new Java SDK language target to the SDK generator.

Files added:

  • src/SDK/Language/Java.php — Language class extending Kotlin, with Java-specific type mappings (Long, Double, List), List.of() array syntax, and overridden returnType Twig filter to use Map<String,
    Object> for generic models
  • templates/java/ — Full Twig template set:
    • Maven build (pom.xml) targeting Java 11+
    • Client.java — OkHttp-based HTTP client returning CompletableFuture for async, chunked file upload support, AppwriteException on errors
    • Service.java — Base service abstraction
    • Per-service ServiceTemplate.java — required + optional param overloads
    • Model.java, RequestModel.java — POJO models with getters, setters, from(Map) factory, toMap()
    • Enum.java — Java enums with getValue() + fromValue()
    • Permission, Role, ID, Query, Operator utility helpers
    • Per-method usage example docs

Files modified:

  • example.php — registered java SDK with io.appwrite namespace
  • tests/Base.php — added java to the io.appwrite namespace condition

Test files added:

  • tests/JavaJava11Test.php — PHPUnit test against eclipse-temurin:11-jdk-jammy
  • tests/JavaJava17Test.php — PHPUnit test against eclipse-temurin:17-jdk-jammy
  • tests/languages/java/Tests.java — standalone test main class covering Foo/Bar/General services, file upload, enums, models, exceptions, OAuth redirect, and all Query/Permission/Role/ID/Operator helpers

Test Plan

  1. Generate the SDK locally:
    php example.php java
    Inspect examples/java/ — all .java source files, enums, models, services, docs, and pom.xml should be present.

  2. Run the test suite (requires Docker):
    docker run --rm -v $(pwd):$(pwd):rw -w $(pwd)
    -v /var/run/docker.sock:/var/run/docker.sock
    php:8.3-cli-alpine sh -c
    "apk add docker-cli && vendor/bin/phpunit --filter JavaJava11Test"

  3. Lint Twig templates:
    composer lint-twig

Related PRs and Issues

No existing issue — this is a new language addition following the process described in CONTRIBUTING.md.

Have you read the https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md?

Yes. The implementation follows the SDK checklist in CONTRIBUTING.md:

  • HTTP client with auth key setters, endpoint setter, self-signed cert support, User-Agent + SDK headers, all global spec headers, addHeader(), call() with GET param concatenation, content-type-aware
    request/response parsing, and AppwriteException on error
  • Service abstraction with constructor taking a Client instance
  • Service classes covering all parameter types (string, integer, boolean, file, array, map)
  • Model classes with getters + setters, from(Map) factory
  • Request model classes with getters + setters + toMap()
  • Per-method usage example docs
  • Tests for Java 11 and Java 17

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR adds a full Java SDK language target to the appwrite SDK generator, including a PHP language class, Twig templates (client, services, models, enums, query/operator helpers, pom.xml), and PHPUnit tests for Java 11 and 17 via Docker. Many of the critical compile and runtime bugs identified in previous review rounds have been addressed in the current revision: the IIFE lambda pattern now correctly casts to Supplier<T>, binary response reads bytes() before the string conversion, parseQueries uses JsonParser.parseString(), spatial/distance query helpers are present, resume-upload now wraps the GET in a try-catch, chunk sizing uses chunkLen, ID.java uses wall-clock microseconds, and optional-param defaults all emit null.

Confidence Score: 3/5

The PR is substantially improved over previous rounds, but several production code paths (generic model responses, nested-query serialization key ordering) remain unverified by the test suite.

The major compile and runtime bugs identified in earlier review rounds (IIFE lambda, binary response corruption, chunked-upload correctness, resume-upload NPE, spatial query helpers, sentinel-value defaults, logging-interceptor dependency) all appear to be fixed in the current revision. No new confirmed P0/P1 issues were found. The remaining uncertainty is that the mock test spec does not exercise generic-model responses (Document) or run the or/and/elemMatch assertions against a live server, so those serialization paths cannot be fully validated without running the generated SDK against the real Appwrite API. This uncertainty prevents a higher score.

templates/java/src/main/java/io/appwrite/Query.java.twig (nested-query key ordering), templates/java/src/main/java/io/appwrite/services/ServiceTemplate.java.twig (generic-model converter cast)

Important Files Changed

Filename Overview
src/SDK/Language/Java.php Language class extending Kotlin; getTypeName, getParamDefault (all emitting null for missing optional defaults), and returnType filter override look correct.
templates/java/src/main/java/io/appwrite/Client.java.twig Binary response reads bytes() before string conversion; chunkedUpload uses chunkLen throughout; resume GET is wrapped in try-catch; small-file path uses Files.readAllBytes. Core issues from previous rounds appear resolved.
templates/java/src/main/java/io/appwrite/Query.java.twig Spatial and distance query methods are present; parseQueries uses JsonParser.parseString(); wrapCoords and withDistance produce the expected nested structure. Key-ordering behaviour of Gson JsonObject (LinkedTreeMap alphabetical sort) may still affect or/and/elemMatch inner-query serialization.
templates/java/src/main/java/io/appwrite/models/Model.java.twig IIFE lambda replaced with explicit Supplier cast (valid Java); separate branches for array-of-sub_schema, array-of-enum, and scalar-enum handle all property combinations correctly.
templates/java/src/main/java/io/appwrite/services/ServiceTemplate.java.twig Overload condition correctly requires at least one required AND one optional param; overload signature iterates over requiredParams only (no trailing-comma bug); call body handles multipart onProgress=null correctly.
templates/java/pom.xml.twig Declares okhttp 4.12.0 and gson 2.10.1 (supports JsonParser.parseString); no logging-interceptor needed; exec plugin uses ${exec.mainClass} set via -D on command line.
tests/languages/java/Tests.java Exception unwrapping correctly uses e.getCause().getCause() for AppwriteException; redirect handled as String; oauth2 uses explicit cast. General test coverage looks reasonable.
tests/JavaJava11Test.php Uses maven:3.9-eclipse-temurin-11 image (includes Maven); correct expected output arrays.
tests/JavaJava17Test.php Uses maven:3.9-eclipse-temurin-17; tests same code as Java 11 variant with a different JDK image.

Reviews (22): Last reviewed commit: "fix(java): use wall-clock nanoseconds fo..." | Re-trigger Greptile

Comment thread templates/java/pom.xml.twig
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig Outdated
Comment thread tests/languages/java/Tests.java
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig
Comment thread tests/JavaJava17Test.php
- Query: add spatial/geo methods (distanceEqual/NotEqual/GreaterThan/LessThan with distance+meters fields, intersects/notIntersects/crosses/notCrosses/overlaps/notOverlaps/touches/notTouches, elemMatch)
- Client: null-check HttpUrl.parse() result to prevent NPE on bad URLs
- Client: fix last-chunk data corruption by using Arrays.copyOfRange/readFully instead of fixed CHUNK_SIZE buffer
- Client: replace FileInputStream resource leak with Files.readAllBytes(Paths.get(...))
- Tests: fix AppwriteException unwrap depth (ExecutionException -> RuntimeException -> AppwriteException requires two getCause() calls)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig Outdated
- Client: read byte[] responses with body().bytes() to avoid charset
  re-encoding that corrupts binary data
- ServiceTemplate: only generate no-optional-args overload when method
  has both required AND optional params; avoids empty-arg overload when
  all params are optional

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread tests/JavaJava11Test.php
- Switch test Docker images from eclipse-temurin (JDK only) to
  maven:3.9-eclipse-temurin-11/17 which bundles both JDK and Maven
- Fix trailing comma in overload method signature by iterating only
  required params using Twig filter instead of relying on loop.last
  over the full parameter list
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig Outdated
Comment thread templates/java/src/main/java/io/appwrite/models/Model.java.twig Outdated
Comment thread templates/java/src/main/java/io/appwrite/models/Model.java.twig Outdated
…mbdas

- Query: replace List.copyOf(List<String>) with new ArrayList<Object>(...)
  for or/and/elemMatch/exists/notExists/select to fix generics invariance
  compile error
- Model/RequestModel: replace bare (() -> {}).call() with
  ((Supplier<T>) () -> {}).get() — untyped IIFE lambdas are invalid Java
  (valid Kotlin only); affects sub_schema array and enum array properties
  in from() and toMap()
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig
…aded

Prevents NPE from converter.apply(null) when resuming an upload that
is already complete (offset >= size means the while loop never runs)
Comment thread templates/java/src/main/java/io/appwrite/Operator.java.twig Outdated
…ator

Java generics are invariant so List<String>/List<Double> cannot be
assigned to List<Object>. Tests.java passes typed lists (List.of(...))
to containsAny, containsAll, all spatial/distance methods in Query,
and arrayAppend/arrayPrepend/arrayIntersect/arrayDiff in Operator.
Changed all public method signatures to List<?> and widen internally
with new ArrayList<Object>(values).
Comment thread tests/languages/java/Tests.java
Comment thread src/SDK/Language/Java.php
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig Outdated
…ection

- Tests: redirect() returns CompletableFuture<String> not Map; remove
  incorrect cast that caused ClassCastException at runtime
- Java.php: change TYPE_INTEGER optional default from -1L to null so
  callers can distinguish unset from a valid negative value
- Client: check extraHeaders first then fall back to global headers map
  for content-type detection instead of ignoring global headers
Comment thread src/SDK/Language/Java.php
Mirrors the Long fix — 1.0 is a valid API value and must not be sent
silently when the caller omits the parameter.
Comment thread src/SDK/Language/Java.php Outdated
Empty string and false are valid API values; using them as defaults
causes unintended parameters to be sent on every call that omits them.
Consistent with the null fix already applied to Long and Double.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 25, 2026

Want your agent to iterate on Greptile's feedback? Try greploops.

Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig
Comment thread templates/java/src/main/java/io/appwrite/models/Model.java.twig
… from()

- Client: check instanceof MultipartBody.Part instead of hardcoded
  key name 'file' so any upload param name works correctly
- Model: add array-of-enum branch before the scalar-enum branch in
  from() so List<EnumType> fields deserialize correctly instead of
  emitting a Supplier<EnumType> compile error
Comment thread templates/java/src/main/java/io/appwrite/models/Model.java.twig Outdated
…odel

For List<EnumType> properties, iterate and call .getValue() on each
element. Without this branch the scalar-enum path called .getValue()
on the List itself, causing a compile error.
Comment thread templates/java/src/main/java/io/appwrite/models/Model.java.twig
…ies constructor

When a model has additionalProperties (generic T data param), the last
regular property was missing its trailing comma, causing a compile
error for all generic response models like Document<T>.
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig Outdated
…tance queries

Remove top-level Gson-annotated distance/meters fields. Instead append
them to the values list via withDistance() helpers so the serialized
JSON matches the expected [[coords],distance,meters] structure that
QUERY_HELPER_RESPONSES asserts.
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig Outdated
CHUNK_SIZE and chunkLen diverge on the final chunk. Use chunkLen so
Content-Range header reflects actual bytes sent and offset advances
by the exact number of bytes read, not a fixed block size.
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig Outdated
Comment thread templates/java/src/main/java/io/appwrite/Client.java.twig
…e GET

- Remove catch(AppwriteException e) from call() — AppwriteException is
  only ever wrapped in RuntimeException inside the try body, so the
  catch clause was an unreachable checked-exception compile error
- Wrap the chunkedUpload resume GET in try-catch so a 404 on brand-new
  uploads is silently ignored and the upload starts from offset 0
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig Outdated
Storing raw JSON strings in the values list causes Gson to re-serialize
them as escaped strings. Parse each query string back to Object via
Gson first so they serialize as nested JSON objects.
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig
… order

gson.fromJson returns a LinkedTreeMap with unpredictable key ordering,
causing re-serialized nested queries to mismatch expected output.
JsonParser.parseString returns a JsonElement that Gson serializes
verbatim, preserving the original key order.
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig Outdated
intersects/notIntersects/crosses/notCrosses/overlaps/notOverlaps/
touches/notTouches were storing flat coords [lat,lon] but expected
output is [[lat,lon]]. Use wrapCoords() to nest the input list as a
single values element.
Comment thread templates/java/src/main/java/io/appwrite/Query.java.twig
…withDistance

Previous format spread coords+distance+meters as flat values elements.
Expected format is values:[[[coords],distance,meters]] — the entire
triplet as one nested element. Build an inner list [coords,distance,meters]
and wrap it in an outer single-element list.
Comment thread templates/java/src/main/java/io/appwrite/ID.java.twig
System.nanoTime() is a monotonic clock unrelated to wall time, giving
wrong sub-second values. now.getNano()/1000 gives 0-999999 microseconds
from the wall clock, correctly filling the 5-hex-digit field and
preserving time-sortability of generated IDs.
@TamsavYT
Copy link
Copy Markdown
Author

Hi team! I've addressed all the issues flagged by the review bot across several rounds — no P0/P1 issues remain. Would love to get maintainer feedback on whether Java SDK support fits the project, and if there's anything else needed before this can move forward. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant