Skip to content

Java client throws IOException for 201 responses due to missing body #136

Open

Description

@steven-barnes
        Account body = new Account();
        body.setName("test3");
        body.setCurrency(Currency.USD);
        body.setExternalKey("89f20977-f937-4bd7-82f9-ac4ef07b9999");
        body.setEmail("test3@comcast.com");
        accountApi.createAccount(body, requestOptions);

This results in an IOException, as there is no response body. Running the code again results in a 409 exception, Account already exists for key 89f20977-f937-4bd7-82f9-ac4ef07b9999, indicating the account was successful created.

When creating similar accounts in the swagger page, I see the response code is 201, but the server does not return a body. The swagger page suggests that an Account object will be returned. This conflicts with the information in the docs, which says that the endpoint will return a URL in the location header.

curl -X POST "https://killbill.dev.commerce.comcast.com/1.0/kb/accounts" -H "accept: application/json" -H "X-Killbill-CreatedBy: testing" -H "authorization: Basic REDACTED" -H "X-Killbill-ApiKey: REDACTED" -H "X-Killbill-ApiSecret: REDACTED" -H "Content-Type: application/json" -d "{ \"name\": \"test99\", \"externalKey\": \"89f20977-f937-4bd7-82f9-ac4ef0999999\", \"email\": \"test99@comcast.com\", \"currency\": \"USD\"}"

Activity

  1. pierre commented on Nov 16, 2024

    @pierre
    Member

    Indeed, Kill Bill doesn't return the body upon creation (https://docs.killbill.io/latest/quick_start_with_kb_api#_step_3_create_an_account), but you can ask the client to fetch it automatically using requestOptions.withFollowLocation(true):

    if (requestOptions.shouldFollowLocation() && response.headers().firstValue("Location").isPresent()) {

    IIRC we could never really tell Swagger about this behaviour.

  2. steven-barnes commented on Nov 18, 2024

    @steven-barnes
    Author

    I have tried that, and still get an IOException:

    RequestOptions.builder
          .withCreatedBy("CS2")
          .withFollowLocation(Boolean.TRUE)
          .build()

    Looking at the code for createAccount, it seems to be setting followLocation to TRUE by default.

  3. pierre commented on Nov 18, 2024

    @pierre
    Member

    Are you following https://docs.killbill.io/latest/java_client ?

    If so, could you share a Main class that reproduces the issue on a vanilla installation? The Java client is used pervasively throughout our test suite, and I don't know of any regression.

  4. steven-barnes commented on Nov 18, 2024

    @steven-barnes
    Author

    I am using the latest version in Maven Central, 1.3.6. I have coded a test in Java, and still get an IOException:

    package org.example;
    
    import org.killbill.billing.catalog.api.Currency;
    import org.killbill.billing.client.*;
    import org.killbill.billing.client.api.gen.*;
    import org.killbill.billing.client.model.gen.Account;
    
    public class Main {
    
        static KillBillHttpClient client = new KillBillHttpClient(REDACTED);
    
        static AccountApi accountApi = new AccountApi(client);
    
        public static void main(String[] args) {
            var requestOptions = RequestOptions.builder()
                    .withCreatedBy("CS2")
                    .withFollowLocation(Boolean.TRUE)
                    .build();
    
            var body = new Account();
            body.setName("test");
            body.setCurrency(Currency.USD);
            body.setExternalKey("xyzzy2");
            body.setEmail("test@comcast.com");
    
            try {
                var result = accountApi.createAccount(body, requestOptions);
                System.out.println(result);
            } catch (KillBillClientException e) {
                e.printStackTrace();
            }
        }
    }
  5. pierre commented on Nov 19, 2024

    @pierre
    Member

    Unable to reproduce.

    • Can you check what's different with the below?
    • Can you check the Kill Bill logs?

    pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <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>
      <groupId>com.mycompany.app</groupId>
      <artifactId>my-app</artifactId>
      <version>1.0-SNAPSHOT</version>
      <name>my-app</name>
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.release>11</maven.compiler.release>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.kill-bill.billing</groupId>
          <artifactId>killbill-client-java</artifactId>
          <version>1.3.6</version>
        </dependency>
      </dependencies>
      <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to
          parent pom) -->
          <plugins>
            <!-- clean lifecycle, see
            https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
            <plugin>
              <artifactId>maven-clean-plugin</artifactId>
              <version>3.4.0</version>
            </plugin>
            <!-- default lifecycle, jar packaging: see
            https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
            <plugin>
              <artifactId>maven-resources-plugin</artifactId>
              <version>3.3.1</version>
            </plugin>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.13.0</version>
            </plugin>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>3.3.0</version>
            </plugin>
            <plugin>
              <artifactId>maven-jar-plugin</artifactId>
              <version>3.4.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-install-plugin</artifactId>
              <version>3.1.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-deploy-plugin</artifactId>
              <version>3.1.2</version>
            </plugin>
            <!-- site lifecycle, see
            https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
            <plugin>
              <artifactId>maven-site-plugin</artifactId>
              <version>3.12.1</version>
            </plugin>
            <plugin>
              <artifactId>maven-project-info-reports-plugin</artifactId>
              <version>3.6.1</version>
            </plugin>
          </plugins>
        </pluginManagement>
        <plugins>
          <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
              <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
              </descriptorRefs>
              <archive>
                <manifest>
                  <mainClass>com.mycompany.app.App</mainClass>
                </manifest>
              </archive>
            </configuration>
            <executions>
              <execution>
                <goals>
                  <goal>single</goal>
                </goals>
                <phase>package</phase>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>
    

    App.java:

    package com.mycompany.app;
    
    import org.killbill.billing.catalog.api.Currency;
    import org.killbill.billing.client.KillBillClientException;
    import org.killbill.billing.client.*;
    import org.killbill.billing.client.api.gen.*;
    import org.killbill.billing.client.model.gen.Account;
    
    public class App {
        public static void main(String[] args) {
            String username = "admin";
            String password = "password";
            String apiKey = "bob";
            String apiSecret = "lazar";
            String serverHost = "localhost";
            int serverPort = 8080;
            String kbServerUrl = String.format("http://%s:%d", serverHost, serverPort);
            KillBillHttpClient killBillHttpClient = new KillBillHttpClient(kbServerUrl, username, password, apiKey,
                    apiSecret);
            AccountApi accountApi = new AccountApi(killBillHttpClient);
    
            var requestOptions = RequestOptions.builder()
                    .withCreatedBy("CS2")
                    .withFollowLocation(Boolean.TRUE)
                    .build();
    
            var body = new Account();
            body.setName("test");
            body.setCurrency(Currency.USD);
            body.setExternalKey("xyzzy");
            body.setEmail("test@comcast.com");
    
            try {
                var result = accountApi.createAccount(body, requestOptions);
                System.out.println(result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    Execution:

    $ java -jar target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
    SLF4J: No SLF4J providers were found.
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
    class Account {
        org.killbill.billing.client.model.gen.Account@fc5e4c1c
        accountId: 5dbaf858-5447-4262-8418-ccae0e26ca31
        name: test
        firstNameLength: null
        externalKey: xyzzy
        email: test@comcast.com
        billCycleDayLocal: 0
        currency: USD
        parentAccountId: null
        isPaymentDelegatedToParent: false
        paymentMethodId: null
        referenceTime: 2024-11-19T08:14:40.000Z
        timeZone: UTC
        address1: null
        address2: null
        postalCode: null
        company: null
        city: null
        state: null
        country: null
        locale: null
        phone: null
        notes: null
        isMigrated: null
        accountBalance: null
        accountCBA: null
        auditLogs: []
    }
    $ java -jar target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
    SLF4J: No SLF4J providers were found.
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
    org.killbill.billing.client.KillBillClientException: Account already exists for key xyzzy
            at org.killbill.billing.client.KillBillHttpClient.throwExceptionOnResponseError(KillBillHttpClient.java:390)
            at org.killbill.billing.client.KillBillHttpClient.doPrepareRequestInternal(KillBillHttpClient.java:349)
            at org.killbill.billing.client.KillBillHttpClient.doPrepareRequest(KillBillHttpClient.java:296)
            at org.killbill.billing.client.KillBillHttpClient.doPost(KillBillHttpClient.java:210)
            at org.killbill.billing.client.KillBillHttpClient.doPost(KillBillHttpClient.java:205)
            at org.killbill.billing.client.api.gen.AccountApi.createAccount(AccountApi.java:176)
            at com.mycompany.app.App.main(App.java:34)
    

    Java version:

    $ java -version
    openjdk version "11.0.14.1" 2022-02-08 LTS
    OpenJDK Runtime Environment Microsoft-31205 (build 11.0.14.1+1-LTS)
    OpenJDK 64-Bit Server VM Microsoft-31205 (build 11.0.14.1+1-LTS, mixed mode)
    
  6. steven-barnes commented on Nov 19, 2024

    @steven-barnes
    Author

    Using the debugger, I can see that the location URL is using http, the request to killbill is using https:

        static KillBillHttpClient client = new KillBillHttpClient(
                "https://killbill.dev.commerce.comcast.com",

    http://killbill.dev.commerce.comcast.com:80/1.0/kb/accounts/16c15fcb-f461-4ae0-baff-ec114883496a

    We are self-hosting KB in AWS, and I am testing from my laptop.

  7. pierre commented on Nov 20, 2024

    @pierre
    Member

    Is the Location header returning HTTP instead of HTTPS? What is terminating SSL, Tomcat or an intermediate load balancer? If the latter, you might need to configure the load balancer to send x-forwarded-proto.

    Related:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions