-
Notifications
You must be signed in to change notification settings - Fork 450
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 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();
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);
}
}
}