Skip to content

Migrating from java apns

Jon Chambers edited this page Jan 28, 2019 · 2 revisions

Migrating from java-apns to Pushy

java-apns is a popular Java library for sending push notifications, but hasn't been actively maintained for several years and is lagging behind major APNs developments. Most significantly, it doesn't support the HTTP/2-based APNs API or token-based authentication.

This migration guide is intended for existing users of java-apns who would like to migrate to Pushy. It lays out major changes in the APNs protocol itself since java-apns ended active development and how java-apns concepts map to concepts in Pushy.

Connections, clients, and services

java-apns is largely organized around the idea of an ApnsService, which is responsible for sending notifications to the APNs server. The equivalent concept in Pushy is the ApnsClient.

Older versions of the APNs protocol/API made it impossible to tell if sending a notification to the APNs server had actually succeeded. As a consequence, APNs "provider libraries" like java-apns and older versions of Pushy had to make tradeoffs—and provide ways for callers to adjust those tradeoffs—around delivery confidence, complexity, and performance. Newer versions of the APNs API based on HTTP/2 acknowledge each and every notification, eliminating the need for those tradeoffs.

Pushy uses the newer HTTP/2-based APNs API, and so many concepts in java-apns (and older versions of Pushy) no longer apply. In particular:

  • The notion of a "feedback service" and getting lists of inactive device tokens is entirely gone
  • The notion of creating a "batched" client is obsolete because flow control and notification acknowledgement are built into the HTTP/2-based APNs API
  • The notion of creating a "queued" client is obsolete because flow control is built into the HTTP/2-based APNs API
  • Explicitly configuring "error detection" is no longer necessary because the HTTP/2-based APNs API reports a definitive status for each notification
  • More robust connection health checks and the end of close-connection-on-rejected-notification behavior in the HTTP/2-based APNs API mean that reconnection policies are obsolete

Of the concepts that remain, the following table provides a mapping of major java-apns concepts to their equivalent in Pushy:

java-apns concept Pushy equivalent
ApnsService ApnsClient
ApnsServiceBuilder ApnsClientBuilder
ApnsNotification ApnsPushNotification
ApnsDelegate A combination of ApnsClientMetricsListener and PushNotificationResponse

The java-apns README offers this example for creating an ApnsService instance:

final ApnsService service = APNS.newService()
    .withSandboxDestination()
    .withCert("/path/to/certificate.p12", "MyCertPassword")
    .build();

The equivalent under Pushy is:

final ApnsClient apnsClient = new ApnsClientBuilder()
    .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
    .setClientCredentials(new File("/path/to/certificate.p12"), "MyCertPassword")
    .build();

Notification payloads

In both java-apns and Pushy, notification payloads are JSON strings (as required by the APNs protocol). Both libraries provide tools for constructing notification payloads.

java-apns offers this example:

final String payload = APNS.newPayload()
    .alertBody("Example!")
    .build();

The equivalent in Pushy is:

final String payload = new ApnsPayloadBuilder()
    .setAlertBody("Example!")
    .buildWithDefaultMaximumLength();

Sending notifications

The ApnsService interface in java-apns offers several methods for sending push notifications, but all revolve around the idea of specifying a payload, destination device token (or tokens), and an optional notification expiration time. Callers may make a single call to a java-apns ApnsService to send the same notification to multiple destiantion devices. Pushy, by contrast, offers a single ApnsClient#sendNotification method.

Because Pushy uses the newer HTTP/2-based APNs API, it can report the status of each individual push notification while java-apns cannot. While the "send notification" methods in java-apns return a copy of the notification that was sent, Pushy's "send notification" method returns a Future that reports the status of the attempt to send a notficiation once that attempt has completed. As a consequence, Pushy doesn't provide built-in tools for sending the same notification to multiple devices (would we return a single Future or a collection of Futures?), but it's trivially easy (and still efficient!) to send notifications in a for loop to achieve the same results.

Here's an example of sending a push notification from java-apns:

final ApnsNotification notification = apnsService.push(token, payload);

To do the same from Pushy:

final String topic = "com.example.myApp";

final SimpleApnsPushNotification pushNotification =
    new SimpleApnsPushNotification(token, topic, payload);

final Future<PushNotificationResponse<SimpleApnsPushNotification>> sendFuture =
    apnsClient.sendNotification(pushNotification);

Note that the java-apns ApnsService#push method may throw a RuntimeException if something goes wrong. Pushy's ApnsClient#sendNotification method will never throw an exception, but the returned Future may report failure due to an exception.

The feedback service

With java-apns, callers need to call ApnsService#getInactiveDevices() periodically to retrieve a list of device tokens that may have uninstalled the destination app from Apple's "feedback service." Because each notification's status is reported immediately under the new HTTP/2-based APNs API and the new API has no notion of a feedback service, Pushy users do not need to poll for expired device tokens. Instead, the PushNotificationResponse produced by the Future returned when sending a notification may have a non-null "token invalidation timestamp" indicating that the token is no longer valid.

Under java-apns, users might have code that looks something like this (assuming you've implemented your own methods called shouldStopSendingToDeviceToken and stopSendingToDeviceToken):

for (final Map.Entry<String, Date> entry : apnsService.getInactiveDevices().entrySet()) {
    final String deviceToken = entry.getKey();
    final Date expirationTimestamp = entry.getValue();
    
    if (shouldStopSendingToDeviceToken(deviceToken, expirationTimestamp)) {
        stopSendingToDeviceToken(deviceToken);
    }
}

With Pushy, the equivalent logic might look something like this:

final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse =
        apnsClient.sendNotification(pushNotification).get();

if (!pushNotificationResponse.isAccepted()) {
    if (pushNotificationResponse.getTokenInvalidationTimestamp() != null) {
        final String deviceToken = pushNotificationResponse.getPushNotification().getToken();
        final String expirationTimestamp = pushNotificationResponse.getTokenInvalidationTimestamp();
    
        if (shouldStopSendingToDeviceToken(deviceToken, expirationTimestamp)) {
            stopSendingToDeviceToken(deviceToken);
        }
    }
}