-
Notifications
You must be signed in to change notification settings - Fork 453
Migrating from java apns
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.
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 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();
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();
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.
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);
}
}
}