Java client throws IOException for 201 responses due to missing body
Account body = new Account();
body.setName("test3");
body.setCurrency(Currency.USD);
body.setExternalKey("89f20977-f937-4bd7-82f9-ac4ef07b9999");
body.setEmail("[email protected]");
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\": \"[email protected]\", \"currency\": \"USD\"}"
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): https://github.com/killbill/killbill-client-java/blob/910bcdc58bbf6be08f4b18c5e3bb826837bdcef9/src/main/java/org/killbill/billing/client/KillBillHttpClient.java#L336
IIRC we could never really tell Swagger about this behaviour.
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.
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.
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("[email protected]");
try {
var result = accountApi.createAccount(body, requestOptions);
System.out.println(result);
} catch (KillBillClientException e) {
e.printStackTrace();
}
}
}
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("[email protected]");
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: [email protected]
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)
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.
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:
- https://github.com/killbill/killbill/issues/566
- https://groups.google.com/g/killbilling-users/c/Ofs6J0oGZpI