Web Push Notifications, also known as the Web Push API, are a service provided by browser vendors that enable web apps to display operating system notifications to end users, even when the browser window or installed app is closed. While this feature has been available for desktop browsers and Android for several years, it has now finally become available for iOS as well.
I have an electricity contract with hourly pricing based on the market price. Lately, due to the geopolitical situation, there have been significant fluctuations in prices. As a self-professed optimization nerd, I find myself constantly seeking ways to improve everything. This extends to my home, where we have an electric sauna, and recently, I became an owner of an electric car. Keeping a close eye on electricity prices has become a bit of an obsession, thanks to Liukuri, a fantastic PWA application developed by my colleague Vesa Nieminen. To preserve my mental well-being, it was time to take it a step further and leverage the Push API for a much-needed enhancement.
The Liukuri project is open source, making it a practical example of how to implement Push Notifications into your Java web application. Even if you are not specifically interested in hourly electricity prices in Finland, you might find value in exploring Web Push notifications and using Liukuri as a reference to implement a similar feature in your own application.
Feature design and architecture
My idea was to allow all Liukuri users to define their custom notifications based on specific price thresholds. There are numerous ways to further develop this feature, but for the initial version, I decided to include an on-off flag, a price threshold, a direction indicator, and the ability to define a custom message.
Screenshot of the UI for editing custom price notifications.
Until now, Liukuri didn’t have any login or user state functionality, but the app needs some key to identify each user. To avoid implementing actual logins at this stage, we have introduced a solution where a random UUID string is generated when a user requests to subscribe to notifications. This unique ID is used in the PostgreSQL database to store the rules associated with the user. Additionally, the UUID is saved in the LocalStorage of the browser/PWA app, enabling users to conveniently edit their existing rules instead of repeatedly defining new ones.
Given that price changes in Finland occur on an hourly basis, scheduling an hourly process was sufficient to check all the saved rules from the database. When necessary, the server triggers the push notification via the stored HTTP endpoint.
How Web Push works: Signup sequence
How Web Push works: Send message sequence
Dependencies & backend
For the backend, we needed five new dependencies:
nl.martijndwars:web-push
- a handy Java library to send web push notifications from your Java code. This is the same library used by an earlier Vaadin + Web Push example.org.bouncycastle:bcprov-jdk15on
- Bouncycastle provides some cryptographic functions needed for web push notifications. Used indirectly via web push.org.springframework.boot:spring-boot-starter-data-jdbc
- Spring Data JDBC felt like a “right-sized” persistence solution for this simple usage case. No need to manually do raw SQL and result set mapping, but no need for full Hibernate either.Org.postgresql:postgresql
- PostgreSQL driver for Spring Data JDBC, not used directly.in.virit:viritin
- Viritin is my handy Swiss army knife for Vaadin UIs. In this case, it is used to implement the notification UI and to provide Java API for WebStorage.
We created two tables in the database. The Subscription table (and similar domain object) holds the HTTP endpoint and the necessary secrets for the server to trigger notifications. The notification table (and its domain object) holds the configured rules set by the user. The Subscription ID is the “user ID” and is also stored in the browser's localStorage. If your system already has the authentication in place, it may be better to use the user’s email or another existing identifier instead of the Subscription ID.
The NotificationService
class has two functions. Firstly, it acts as a gateway for the UI to read and write the notification rules that the user adjusts. Additionally, using the handy @Scheduled
annotation in Spring, it executes other services in the application on an hourly basis. During this process, it checks the saved notification rules and determines whether an action notification should be triggered.
The following Java code snippet demonstrates how the server sends messages using the web-push library.
Iterable<PriceNotification> all = repository.findAll();
all.forEach(pn -> {
if(pn.isEnabled() && pn.isUp() == up && pn.getPrice() >= lowerBound && pn.getPrice() <= upperBound) {
String body =
"Price now %.2f c/kWh. %s".formatted(priceNow, pn.getExtraMsg())
+ peakLowMsg;
String title = up ? "Prices going up!": "Prices going down!";
Message message = new Message(title, body);
Subscription s = getSubscription(pn.getSubscriptionId());
sendNotification(s, message);
}
});
Browser integration
On the browser side, we need to request permission from the user to send notifications. One tricky thing here is that the request must happen within a JavaScript “thread” initiated by direct user interaction. Therefore, we cannot use the pure Java click listener via Vaadin or execute the command when the user navigates to the view, for example. To handle this, if the user has not yet subscribed, we include a button with a direct JavaScript click listener. This listener requests permission using the Web Push Notification API.
The API requires the server’s “VAPID” public key, which we fetch via the NotificationService class. This class ultimately fetches the key from environment variables using Spring Boot’s externalized configuration. In our previous example, we provided instructions on how to generate the VAPID keys for your own application.
Once the user grants permissions, we save the web push endpoint and cryptographic keys to the server using a custom event. In the listener, we pass these values to the service, allowing the UI to display the editing UI effectively.
UI implementation
If you are familiar with Vaadin, the actual UI implementation is quite simple. Essentially, we are editing a java.util.List
of PriceNotification
objects. To edit that kind of list, I’m using the ElementCollectionField from Viritin. It only needs another helper class that defines the components used to edit individual properties of PriceNotifications
. New rows, removing rows, and data binding then happen automatically. 😎
When the user hits the save button in the UI, the updated list is once again passed to the service. The service then saves the updated notifications to the PostgreSQL using the Spring Data JDBC repository and, additionally, removes any orphaned rows.
Got questions? Leave a comment below, or connect with the growing Vaadin community on our Discord server.
New to Vaadin? Download and get started on a new project at start.vaadin.com!