Java
The Amberflo Java SDK makes it easy to integrate usage metering into your Java applications. It is optimized for high-throughput environments and provides built-in support for batching, background flushing, and graceful shutdown.
The SDK works seamlessly with popular Java frameworks such as Spring and Tomcat, making it simple to instrument your existing services with minimal configuration.
Github repo with Java SDK samples:

Simple Main class example:

Maven:

<!-- https://mvnrepository.com/artifact/io.amberflo/metering-java-client -->
<dependency>
<groupId>io.amberflo</groupId>
<artifactId>metering-java-client</artifactId>
</dependency>Batching Records
The Amberflo Java SDK is designed for high-throughput environments. You can safely submit hundreds of meter records per second, making it suitable for deployment in systems handling large volumes of requests, such as web servers processing hundreds of requests per second.
Each call to the SDK does not immediately result in an HTTP request. Instead, meter events are added to an in-memory queue. These events are then batched and sent in the background, allowing for significantly faster and more efficient operation.
Batching behavior can be customized. By default:
- The SDK flushes the queue after 100 messages (maxBatchSize)
- Or after 0.5 seconds have passed since the last flush (maxDelayInSec), whichever comes first
Handling High Volume
If the SDK detects that it is receiving messages faster than it can flush them, it will stop accepting new meter records. This prevents the queue from growing indefinitely and protects your application from crashing due to memory overload.
- The maximum number of meter records held in memory is 100,000 (maxAsyncQueueSize)
This approach ensures that your application remains stable, even under heavy load.
Manual Flush (Blocking Call)
You can trigger a manual flush to ensure all remaining events are sent before your application shuts down. This is especially useful in scripts, background jobs, or short-lived services.
MeteringContext.flushAndClose();
Note: This is a blocking call. It will pause the current thread until all queued messages are sent. Use this method only during cleanup or shutdown, not during normal request processing.
Writing to S3
The Java SDK also supports writing meter records directly to Amazon S3. Amberflo will automatically retrieve and process these records from your configured S3 bucket.
1. Create a metering client from configuration file
/***
Full sample at
https://github.com/amberflo/metering-java-sample/blob/main/metering-sample/src/main/java/demo/ingest/S3MeteringExamples.java
***/
import com.amberflo.metering.ingest.MeteringContext;
import com.amberflo.metering.ingest.meter_message.MeterMessage;
import com.amberflo.metering.ingest.meter_message.MeterMessageBuilder;
import java.time.LocalDateTime;
import static com.amberflo.metering.ingest.MeteringContext.metering;
/**
* In this app we describe different ways to call the S3metering services:
* 1. Builder.
* 2. Factory.
**
* For more info about each of our api please see the java doc.
*/
public class S3MeteringExamples {
private final static String METER_NAME = "TrancsactionCount";
private final static int METER_VALUE = 2210;
private final static String CUSTOMER_ID = "Batman";
private final static LocalDateTime EVENT_TIME = LocalDateTime.now();
private final static String S3_METERING_CONFIG_FILE = "s3-metering.json";
private final static String ACCESS_KEY = "accessKey";
private final static String SECRET_KEY = "secretKey";
private final static String BUCKET_NAME = "bucketName";
public static void main(String[] args) {
createS3MeteringClientWithCredentials();
}
/**
* A way to to create S3Metering client using the config file by specifying the clientType, bucketName,
* httpRetriesCount, httpTimeOutSeconds, isAsync parameters in the json file"
*/
private static void createS3MeteringClientWithFile() throws Exception {
try(MeteringContext context = MeteringContext.contextWithS3ClientFromFile(S3_METERING_CONFIG_FILE)) {
final MeterMessage meter = MeterMessageBuilder
.createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
.setMeterValue(METER_VALUE)
.build();
metering().meter(meter);
}
}
/**
* A way to to create S3Metering client using the bucket name, access key, secret key, retries, timeout
* to the context.
*/
private static void createS3MeteringClientWithCredentials() {
try(MeteringContext context = MeteringContext.contextWithS3Client(BUCKET_NAME, ACCESS_KEY, SECRET_KEY,
5, 30)) {
final MeterMessage meter = MeterMessageBuilder
.createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
.setMeterValue(METER_VALUE)
.build();
metering().meter(meter);
}
}
}2. Create a S3 metering client with with credentials from code
/***
Full sample at
https://github.com/amberflo/metering-java-sample/blob/main/metering-sample/src/main/java/demo/ingest/S3MeteringExamples.java
***/
import com.amberflo.metering.ingest.MeteringContext;
import com.amberflo.metering.ingest.meter_message.MeterMessage;
import com.amberflo.metering.ingest.meter_message.MeterMessageBuilder;
import java.time.LocalDateTime;
import static com.amberflo.metering.ingest.MeteringContext.metering;
/**
* In this app we describe different ways to call the S3metering services:
* 1. Builder.
* 2. Factory.
**
* For more info about each of our api please see the java doc.
*/
public class S3MeteringExamples {
private final static String METER_NAME = "TrancsactionCount";
private final static int METER_VALUE = 2210;
private final static String CUSTOMER_ID = "Batman";
private final static LocalDateTime EVENT_TIME = LocalDateTime.now();
private final static String S3_METERING_CONFIG_FILE = "s3-metering.json";
private final static String ACCESS_KEY = "accessKey";
private final static String SECRET_KEY = "secretKey";
private final static String BUCKET_NAME = "bucketName";
public static void main(String[] args) {
createS3MeteringClientWithCredentials();
}
/**
* A way to to create S3Metering client using the config file by specifying the clientType, bucketName,
* httpRetriesCount, httpTimeOutSeconds, isAsync parameters in the json file"
*/
private static void createS3MeteringClientWithFile() throws Exception {
try(MeteringContext context = MeteringContext.contextWithS3ClientFromFile(S3_METERING_CONFIG_FILE)) {
final MeterMessage meter = MeterMessageBuilder
.createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
.setMeterValue(METER_VALUE)
.build();
metering().meter(meter);
}
}
/**
* A way to to create S3Metering client using the bucket name, access key, secret key, retries, timeout
* to the context.
*/
private static void createS3MeteringClientWithCredentials() {
try(MeteringContext context = MeteringContext.contextWithS3Client(BUCKET_NAME, ACCESS_KEY, SECRET_KEY,
5, 30)) {
final MeterMessage meter = MeterMessageBuilder
.createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
.setMeterValue(METER_VALUE)
.build();
metering().meter(meter);
}
}
}Code examples
Simple ingest meter
final Double meterValue = 2D;
final LocalDateTime accurateMeterTime = LocalDateTime.now(ZoneOffset.UTC);
final Map<String, String> dimensions = new HashMap<>();
System.setProperty(MeteringContext.METERING_DOMAIN, Domain.Prod.name());
// Use the app key for authentication
//MeteringContext context = MeteringContext.createOrReplaceContext("appKey")
MeteringContext.createOrReplaceContext("appKey",
"myApp", Domain.Prod,Region.US_West, 1, 10);
// Simple meter call. This call is non blocking and batched for performance
context.meteringInstance().meter("customerId", "meterName",meterValue,accurateMeterTime,dimensions);Ingest examples
https://github.com/amberflo/metering-java-sample/tree/main/metering-sample/src/main/java/demo/ingest
package demo;
import com.amberflo.metering.ingest.MeteringContext;
import com.amberflo.metering.ingest.extensions.ServiceMetering;
import com.amberflo.metering.ingest.meter_message.Domain;
import com.amberflo.metering.ingest.meter_message.MeterMessage;
import com.amberflo.metering.ingest.meter_message.MeterMessageBuilder;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Map;
import com.amberflo.metering.ingest.meter_message.Region;
import static com.amberflo.metering.ingest.MeteringContext.metering;
import static com.amberflo.metering.ingest.extensions.ServiceMetering.serviceMetering;
import static com.amberflo.metering.ingest.extensions.CustomerMetering.customerMetering;
/**
* In this app we describe different ways to call the metering services:
* 1. Builder.
* 2. Factory.
* 3. Templates.
*
* The example itself used the {@link Domain#Prod} configuration to setup the metering client.
*
* For more info about each of our api please see the java doc.
*/
public class MeteringExamples {
private final static String METER_NAME = "TrancsactionCount";
private final static int METER_VALUE = 3;
private final static String CUSTOMER_ID = "YWJjNDU2";
private final static String SERVICE_CALL = "process-request";
private final static String SERVICE_NAME = "magic-service";
private final static LocalDateTime EVENT_TIME = LocalDateTime.now(ZoneOffset.UTC);
private final static Double MB_USED = 15.5;
private final static double PROCESSING_TIME_MILLIS = 100;
private final static boolean ERROR_FLAG = true;
private final static Map<String, String> EXTRA_DIMENSIONS_MAP = null;
public static void main(String[] args) throws Exception {
// Step 1 - setting up the domain (prod dev)
// You need to set the "metering domain" system env var to prod.
System.setProperty(MeteringContext.METERING_DOMAIN, Domain.Prod.name());
// Step 2 - send some meters to amberflo.
sendSomeMeters();
// Step 3 - flush and close the metering client.
// For efficiency, we send meters to amberflo in batches. You can configure the max batch size in
// the prod/def metering.json file. Having said that, even if you use a max batch size of 1, you still want
// to call this method, especially if use configured 'isAsync' to true.
MeteringContext.flushAndClose();
// Comment: 'Metering' is autoClosable - so instead of step 2 and 3, you can also send meters this way:
try (final MeteringContext context = MeteringContext.getContext()) {
// templates are just and abstraction layer on the top of the metering.
customerMetering().signUp(CUSTOMER_ID, EVENT_TIME);
}
}
/**
* Here we describe the 3 methods to directly send meters:
* 1. Builder.
* 2. Factory.
* 3. Templates.
*/
private static void sendSomeMeters() {
// Option 1: Create a custom meter with a builder.
// This option gives you more flexibility regarding the way you construct your meter event, but it
// requires more effort on your side, and might be more errors prone than other options.
// Use it only if you need a completely custom meter.
final MeterMessage meter = MeterMessageBuilder
.createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
.setMeterValue(METER_VALUE)
.build();
metering().meter(meter);
// Look at this method to see a more advanced example for how to set a meter this way.
customBuilderAdvanced();
// Option 2: Create a custom message with a factory
// A factory is a bit more structured way of creating a meter but less flexible. It lets you send
// a meter which contains the:
// a. The customer id - Mandatory (unless you invoke the method within a thread context with
// customer info). This is the id of your customer who made a call to your service (let's say).
// b. The meter name and value - Required.
// c. The start_time - Optional. this can be a start time of a call, or the event time which is relevant
// for the meter. If the start time is null then the event time will by the current time when sending
// the meter.
// d. The extra dimensions map - Optional. this is just a map of string keys to string values containing
// important dimensions.
metering().meter(CUSTOMER_ID, METER_NAME, METER_VALUE, EVENT_TIME, EXTRA_DIMENSIONS_MAP);
metering().meter(CUSTOMER_ID, METER_NAME, METER_VALUE, EVENT_TIME, null);
// Option 3: Templates
// Templates are the most convenient but least flexible option of creating meters explicitly. Basically
// templates are predefined meter types, with a predefined meter name, and predefined meter structure.
//
// Why do we need templates ?
// Let's assume we want to create meters for a service. What would we try to measure ? ... probably we would
// want to record commons things such as 'service calls', 'data usage' or 'processing time'. Now, we don't
// want you to reinvent the will for such common use cases, so we created a few domain-specific factories that
// allows you to create meters more easily.
// Option 3.1 - Service-Metering:
// use the serviceMetering to create
serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME);
serviceMetering().processingTime(CUSTOMER_ID, SERVICE_CALL, PROCESSING_TIME_MILLIS, EVENT_TIME);
serviceMetering().dataUsage(CUSTOMER_ID, SERVICE_CALL, MB_USED, EVENT_TIME);
// See the method below for more advanced\detailed examples of using the meters.
serviceMeteringAdvanced();
// Option 3.2 - Customer-Metering.
// This templates allow you to create customer (client) related meters.
customerMetering().signUp(CUSTOMER_ID, EVENT_TIME);
customerMetering().onboarded(CUSTOMER_ID, EVENT_TIME);
customerMetering().offboarded(CUSTOMER_ID, EVENT_TIME);
customerMetering().login(CUSTOMER_ID, EVENT_TIME);
// This meter described a case a customer registered to your service, but you rejected the registration
// (false-data or a malicious customer for example).
customerMetering().onboardingRejected(CUSTOMER_ID, EVENT_TIME);
}
private static void customBuilderAdvanced() {
final Map<String, String> sessionInfo = new HashMap<>();
sessionInfo.put("session", "789");
final Map<String, String> countryAndStateInfo = new HashMap<>();
sessionInfo.put("country", "US");
sessionInfo.put("state", "WA");
final MeterMessage meter = MeterMessageBuilder
.createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
// meter value is actually optional param. If not set the the meter value will be 1.
.setMeterValue(METER_VALUE)
// if you want to capture the end time of the meter event (now), and measure the duration of
// it, you can use the this method which will set up the duration as the time diff in millis between
// now and the START_TIME. Calling this method will set the meter-type to "Millis".
.captureEndTimeAndDuration() // in our case the duration should be ~3 minutes (in millis).
// meter type isn't currently in use by amberflo. Yet if you feel the need to have it
// use it ... we will notice that and adapt our system accordingly.
.setMeterType("millis")
// Set up service related data.
// Service name is a param to the 'DirectMeteringClient', nevertheless, you can override it here.
.setServiceName(SERVICE_NAME)
.setServiceCall(SERVICE_CALL)
// If you want to mark your meter as error related you can call.
.asError() /* or */ .asError(IllegalArgumentException.class)
// you can set up a region.
.setRegion(Region.US_West)
// The dimensions Map gives you the option to add more properties to your meter, which don't
// exist as part of the predefined methods of the builder.
// For example, let's assume you want to add session info to your meter to track a process
// or customer request e2e, you can add this dimensions this way:
.setDimensionsMap(sessionInfo)
// Another example for custom dimensions:
// Let's assume you want to measure customer related events. As part of that you want to partition
// your customers by country and state. So you can add these two as custom attributes:
.setDimensionsMap(countryAndStateInfo)
// ^^ it's ok call setDimensionsMap multiple times as long as there is no intersection between
// the keys of the maps.
.build();
metering().meter(meter);
}
/**
* The service-metering template contains the following:
*
* {@link ServiceMetering#CALL}:
* 1. CallCompleted (successfully).
* 2. CallError.
* 3. Call - this one is to try and measure a call regardless if it completed successfully or with an error.
* All of these types of calls will produce a {@link ServiceMetering#CALL} meter as they indicates the event of finish
* handling a call (with or without an error).
*
* Other types of calls:
* There are 3 other more specific type of call which will produce different meter
* 1. CallStarted - Will produce a {@link ServiceMetering#CALL_STARTED} meter. Use this event if you want to
* have different meters for the start and the end of a call.
* 2. DataUsage - measure data used by the client (in Mb). Will produce a {@link ServiceMetering#CALL_DATA_USAGE} meter.
* 3. ProcessingTime - Time it took to process a service call request (in millis). Will produce a
* {@link ServiceMetering#CALL_PROCESSING_TIME} meter.
*/
private static void serviceMeteringAdvanced() {
// 'call' will produce a meter called "Call" and it can be used in many ways:
serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME);
serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, ERROR_FLAG, EVENT_TIME); // if you want to mark it as an error
serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, ERROR_FLAG, IllegalAccessError.class, EVENT_TIME); // error + error type
// The SERVICE_CALL in the example above isn't the meter name but a dimension.
// If you find it more convenient you can also record a service call using
serviceMetering().callCompleted(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME); // To mark the end of a call that completed successfully.
serviceMetering().callError(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME); // To mark the end of a call that completed with an error.
// We will see soon how these can be used.
// 'callStarted' as the name suggests, measure an event of start handling a service call regardless of the
// results (the call completed successfully or not).
serviceMetering().callStarted(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME);
// 'processingTime' - measures the time it took to process the call in millis.
serviceMetering().processingTime(CUSTOMER_ID, SERVICE_CALL, PROCESSING_TIME_MILLIS, EVENT_TIME);
// 'dataUsage' - as the name suggests, measures the data returned to the client or used by the call in Mb.
serviceMetering().dataUsage(CUSTOMER_ID, SERVICE_CALL, MB_USED, EVENT_TIME);
// Examples of using multiple of the calls above:
final Runnable methodToRun = () -> {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
serviceMeteringMultiCalls(methodToRun);
}
/**
* You can have a similar decorator/interceptor method to this one in your code.
*/
private static void serviceMeteringMultiCalls(final Runnable runnable) {
final LocalDateTime startTime = LocalDateTime.now(ZoneOffset.UTC);
LocalDateTime endTime = startTime;
try {
serviceMetering().callStarted(CUSTOMER_ID, SERVICE_CALL, startTime);
runnable.run();
endTime = LocalDateTime.now(ZoneOffset.UTC);
serviceMetering().callCompleted(CUSTOMER_ID, SERVICE_CALL, endTime);
} catch (final Exception e) {
endTime = LocalDateTime.now(ZoneOffset.UTC);
serviceMetering().callError(CUSTOMER_ID, SERVICE_CALL, e.getClass(), endTime);
} finally {
final long durationInMillis =
endTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() -
startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
serviceMetering().processingTime(CUSTOMER_ID, SERVICE_CALL, durationInMillis, endTime);
}
}
}Usage example
package demo.usage;
import com.amberflo.metering.usage.clients.UsageClient;
import com.amberflo.metering.usage.model.request.*;
import com.amberflo.metering.usage.model.response.DetailedMeterAggregation;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
/**
* An example app to show the main use-cases and rules of the 'Usage-Api'.
*/
public class UsageExample {
private static final boolean ASCENDING = true;
public static final String CUSTOMER_ID_FIELD = "customerId";
public static void main(final String[] args) {
// Step 1 - create the client
final String appKey = System.getProperty("AMBERFLO_APP_KEY");
final UsageClient usageClient = new UsageClient(appKey);
// Step 2 - query the system
// If you want simple statistics for all of the official meters in your account use the 'all' api.
queryTheAllApi(usageClient);
// If you want to create a custom-query for any specific meter use the "get" api.
queryTheGetApi(usageClient);
// For a batch of custom-queries use the "batch" api.
queryTheGetBatchApi(usageClient);
}
private static void queryTheAllApi(final UsageClient usageClient) {
// Creating a simple 'ALL' api request can be pretty simple.
// 1. Use one of the TimeRangeFactory methods to easily create a time-range for your query.
// 2. Use the AllMetersAggregationsRequestBuilder to create a request.
// 3. Provide the all-request to the usage-client and get the results.
final TimeRange timeRange1 = TimeRangeFactory.yearToDate();
final AllMetersAggregationsRequest request1 = AllMetersAggregationsRequestBuilder.instance(timeRange1).build();
// Each item in the list corresponds to an official 'meter' defined by you in advance.
final List<DetailedMeterAggregation> meterAggregations1 = usageClient.getAll(request1);
System.out.println(meterAggregations1);
// You can also look at X days backwards (for example).
// The start time of the result will be (now - X days) truncated to the beginning of the day.
// For example if now is 3/27/2021-02:53 and X = 4 then the result of calling this method is 3/23/2021-00:00.
final TimeRange timeRange2 = TimeRangeFactory.truncatedLastDays(4);
final AllMetersAggregationsRequest request2 = AllMetersAggregationsRequestBuilder.instance(timeRange2).build();
final List<DetailedMeterAggregation> meterAggregations2 = usageClient.getAll(request2);
// You can also look at X month backwards (for example).
// The start time of the result will be (now - X days) truncated to the beginning of the MONTH.
// For example if now is 3/27/2021-02:53 and X = 2 then the result of calling this method is 1/1/2021-00:00.
final TimeRange timeRange3 = TimeRangeFactory.truncatedLastMonths(2);
final AllMetersAggregationsRequest request3 = AllMetersAggregationsRequestBuilder.instance(timeRange3).build();
final List<DetailedMeterAggregation> meterAggregations3 = usageClient.getAll(request3);
// truncatedLastWeeks and truncatedLastHours have similar truncation logic.
// The reason we truncate is related to the way the usage-api works. Let's look at the example below and then
// explain the reason.
final TimeRange timeRange4 = TimeRangeFactory.truncatedLastDays(4);
final AllMetersAggregationsRequest request4 =
AllMetersAggregationsRequestBuilder.instance(timeRange4)
.setTimeGroupingInterval(AggregationInterval.DAY).build();
final List<DetailedMeterAggregation> meterAggregations4 = usageClient.getAll(request4);
// As you can see in the example above we asked for the results with a TimeGroupingInterval of a day.
// Asking this will cause the usage api to provide you with statistics for each day of the time range you
// asked for. For example, if you asked for the time range {3/23/2021-05:00 - today}, then for each meter you
// will get a statistic for the entire time range as well as a statistic for each day starting from hour 00:00.
// So basically the usage-api will provide you with statistics for the time-range of {3/23/2021-05:00 - today}.
// To make it less confusing, we provide you with "truncated" methods which already produce the truncated
// hour/day/week/month on the client side.
// The truncated methods can also help you getting the beginning of the week (let's say).
final TimeRange timeRange5 = TimeRangeFactory.truncatedLastWeeks(0);
final AllMetersAggregationsRequest request5 =
AllMetersAggregationsRequestBuilder.instance(timeRange5)
.setTimeGroupingInterval(AggregationInterval.DAY).build();
final List<DetailedMeterAggregation> meterAggregations5 = usageClient.getAll(request5);
// Using the all api you can ask to filter the meters for a certain customer id.
final TimeRange timeRange6 = TimeRangeFactory.truncatedLastWeeks(0);
final AllMetersAggregationsRequest request6 =
AllMetersAggregationsRequestBuilder.instance(timeRange6)
.setFilterByCustomerId("customer-id-123")
.setTimeGroupingInterval(AggregationInterval.DAY).build();
final List<DetailedMeterAggregation> meterAggregations6 = usageClient.getAll(request6);
// Last, regarding the time-range we will mention that you can define a custom time range for your query.
// Just notice that the usage-api extends the time range you provide according to the provided
// AggregationInterval (or to AggregationInterval.Hour if no AggregationInterval was provided).
// So for example a query with a time of {3/23/2021-05:21 - 3/29/2021-08:21} and a AggregationInterval.DAY
// will be treated as {3/23/2021-00:00 - 3/30/2021-00:00} and a AggregationInterval.DAY.
final TimeRange timeRange = TimeRange.builder()
.startTimeInSeconds(OffsetDateTime.now().minusMonths(2).toEpochSecond())
.endTimeInSeconds(OffsetDateTime.now().minusMonths(1).toEpochSecond()).build();
final AllMetersAggregationsRequest request =
AllMetersAggregationsRequestBuilder.instance(timeRange)
.setTimeGroupingInterval(AggregationInterval.DAY).build();
final List<DetailedMeterAggregation> meterAggregations = usageClient.getAll(request);
}
private static void queryTheGetApi(final UsageClient usageClient) {
// Querying the get api is the custom and more advanced way of querying the usage-api.
// At the very least you need to provide the: meter-name, aggregation type, and time range.
final String meterApiName = "myMeter";
final TimeRange timeRange = TimeRangeFactory.yearToDate();
final MeterAggregationMetadata request1 =
MeterAggregationMetadataBuilder.instance(meterApiName, AggregationType.SUM, timeRange).build();
final DetailedMeterAggregation result1 = usageClient.get(request1);
System.out.println(result1);
// You can use all available meter types, just be aware that (currently) Amberflo has indexes only
// for the official 'AggregationType' of your meter. So querying for a different type might
// cause the query to be slower.
final MeterAggregationMetadata request2 =
MeterAggregationMetadataBuilder.instance(meterApiName, AggregationType.MIN, timeRange).build();
final DetailedMeterAggregation result2 = usageClient.get(request2);
// You can ask to partition the results by a certain field(s). When doing this Amberflo will provide
// you with statistics per group.
final MeterAggregationMetadata request3 =
MeterAggregationMetadataBuilder.instance(meterApiName, AggregationType.SUM, timeRange)
.setGroupBy(List.of(CUSTOMER_ID_FIELD)).build();
final DetailedMeterAggregation result3 = usageClient.get(request3);
// Partitioning can yield a lot of results, and there is a size limit of 6Mb for the entire serialized
// DetailedMeterAggregation response. So you better add a 'take' clause when partitioning.
// The query below will provide you with statistics for the top 5 customers with the highest meter sum for the
// time range you provided.
final MeterAggregationMetadata request4 =
MeterAggregationMetadataBuilder.instance(meterApiName, AggregationType.SUM, timeRange)
.setGroupBy(List.of(CUSTOMER_ID_FIELD))
.setTake(new Take(5, !ASCENDING))
.build();
final DetailedMeterAggregation result4 = usageClient.get(request4);
// As with the 'all' api you can also ask to partition each group of statistics by units of time.
// In the example below we the usage-api will return you statistics for the top 5 customer, where for each
// customer you will get statistics for the entire time range, as well as for each day.
// Just notice that the usage-api extends the time range you provide according to the provided
// AggregationInterval (or to AggregationInterval.Hour if no AggregationInterval was provided).
// So for example a query with a time of {3/23/2021-05:21 - 3/29/2021-08:21} and a AggregationInterval.DAY
// will be treated as {3/23/2021-00:00 - 3/30/2021-00:00} and a AggregationInterval.DAY.
final MeterAggregationMetadata request5 =
MeterAggregationMetadataBuilder.instance(meterApiName, AggregationType.SUM, timeRange)
.setTimeGroupingInterval(AggregationInterval.DAY)
.setGroupBy(List.of(CUSTOMER_ID_FIELD))
.setTake(new Take(5, !ASCENDING))
.build();
final DetailedMeterAggregation result5 = usageClient.get(request5);
// Last we will mention that you can define filters for your query.
// The example below yields statistics for the top 5 "premium" or "gold" customers in the US.
// Assuming you have this fields - if you don't the usage api will throw an exception.
try {
final MeterAggregationMetadata request6 =
MeterAggregationMetadataBuilder.instance(meterApiName, AggregationType.SUM, timeRange)
.setTimeGroupingInterval(AggregationInterval.DAY)
.setGroupBy(List.of(CUSTOMER_ID_FIELD))
.setFilter(Map.of("customerTier", List.of("Premium", "Gold"), "country", List.of("US")))
.setTake(new Take(5, !ASCENDING))
.build();
final DetailedMeterAggregation result6 = usageClient.get(request6);
} catch (final RuntimeException exception) {
System.out.println(exception);
}
}
private static void queryTheGetBatchApi(final UsageClient usageClient) {
// The "batch" api is exactly the same as the "get" api only the you provide it with a batch
// of request and it return you a batch of results.
final String meterApiName1 = "myMeter1";
final String meterApiName2= "myMeter2";
final TimeRange timeRange = TimeRangeFactory.yearToDate();
final MeterAggregationMetadata request1 =
MeterAggregationMetadataBuilder.instance(meterApiName1, AggregationType.SUM, timeRange).build();
final MeterAggregationMetadata request2 =
MeterAggregationMetadataBuilder.instance(meterApiName2, AggregationType.SUM, timeRange).build();
final List<DetailedMeterAggregation> result1 = usageClient.getBatch(List.of(request1, request2));
System.out.println(result1);
}Create a customer
https://github.com/amberflo/metering-java-sample/blob/main/metering-sample/src/main/java/demo/customer/CustomerDetailsExample.java
Configuration example
{
"clientType": "DirectClient",
"maxAsyncQueyeSize": 20000,
"isAsync": true,
"params": {
"maxDelayInSec": 0.5,
"apiKey": "Y2hhbmdlbWU=",
"accountName": "myAccount",
"maxBatchSize": 100,
"serviceName": "myservicename"
}
}