Creating an Investment Notifier using Java: A Beginner's Experience

Dhiman SealDhiman Seal
10 min read

Introduction

As a beginner programmer, I always found myself drawn to complex projects that seemed way out of my league. However, I recently realized that there’s something to be said for taking a step back and building something simple and fun. That’s why I would like to write about my first Core Java Project: The time I had decided to create an “Investment Notifier” using pure, core Java.

Learning Achieved

While it is a basic project, I did learn a lot of valuable Java skills through it, which I can now share with everyone reading this:

  1. Structuring Java project (used MVC architecture without a proper View, as it is a CLI application). Separated different logic blocks.

  2. Parsing and utilizing CLI parameters passed to a Java Application.

  3. Making Network GET/POST requests to APIs just using core Java utilities, and handling responses.

  4. Using java.awt.* for notifications.

  5. Providing succinct documentation in Java Code.

  6. Error throwing and handling.

  7. Parsing and forming JSON without external libraries (pain).

  8. Abstracting class members to allow read, but not write after object creation.

  9. Java String building, access modifiers, etc.

Description

The idea behind the Investment Notifier was to create a CLI application that would notify me when the price of a specific stock or cryptocurrency crossed a certain threshold. I wanted to learn more about Java and put my skills to the test, and I figured this project would be a great way to do both.

Model-View-Controller (MVC) Architecture

I started by structuring my project using the Model-View-Controller (MVC) architecture. I separated the different logic blocks and made sure everything was easy to follow.

Let’s break down the Model-View-Controller Architecture aspect, which is one of the fundamental architectural patterns one might follow in such a project

Model

Here are some examples of Data Models you might use when creating such an application:

package com.dhi13man.investment_notifier.models;

/**
 * Data Model storing Stock data.
 */
public class Stock {
    private final String code;
    /**
     * @return the Code of the Stock
     */
    public String getCode() { return code; }

    private final String name;
    /**
     * @return the Name of the Stock
     */
    public String getName() { return name; }

    private final double priceUpperThreshold;
    /**
     * @return the price above which script will send Notification
     */
    public double getPriceUpperThreshold() { return priceUpperThreshold; }

    private final double priceLowerThreshold;
    /**
     * @return the price below which script will send Notification
     */
    public double getPriceLowerThreshold() { return priceLowerThreshold; }

    /**
     * The constructor of the Data Model storing Stock data.
     * @param stockCode The Code of the Stock.
     * @param stockName The Name of the Stock.
     * @param stockPriceLowerThreshold The script will send Notification if the price of the Stock is below this.
     * @param stockPriceUpperThreshold The script will send Notification if the price of the Stock is above this.
     */
    public Stock(String stockCode, String stockName, double stockPriceLowerThreshold, double stockPriceUpperThreshold) {
        this.code = stockCode;
        this.name = stockName;
        this.priceLowerThreshold = stockPriceLowerThreshold;
        his.tpriceUpperThreshold = stockPriceUpperThreshold;
    }
}

Although this might seem simple at first, learning how to make immutable data-classes is fundamental in programming, and without it, in any large project things would quickly spin out of control.

View

As it was a CLI application, the view aspect was pretty simple. It was a simple CLI capable of taking inputs. I learned how to parse and utilize CLI parameters passed to a Java application. This was a bit tricky at first, but I soon got the hang of it.

Another aspect of the “view” layer was that I had to utilize core java.awt.* library to create notifications on my desktop whenever a certain threshold was reached. This was a fun feature to add, and it really made the project feel more polished.

package com.dhi13man.investment_notifier.interfaces;

import java.awt.*;
import java.awt.TrayIcon.MessageType;

/**
 * Interface Class that handles Notification sending.
 * Uses Core Java awt to show Desktop Notifications.
 */
public class NotificationInterface {
    /**
     * Class has only Static methods. Do not instantiate. Call the methods from the outside.
     */
    private NotificationInterface() {
        throw new IllegalStateException("Not instantiable");
    }

    /**
     * Send a notification to the notification POST_URL endpoint which this class was initialized with.
     * The backend then sends the notification where required.
     * @param notificationTitle The title of the Notification
     * @param notificationBody The main message body of the Notification
     * @param iconURL What should be the icon of the Notification.
     * @throws AWTException as the function makes use of java.awt.* to show notifications.
     */
    public static void sendNotification(
            String notificationTitle,
            String notificationBody,
            String iconURL
    ) throws AWTException {
        //Obtain only one instance of the SystemTray object
        final SystemTray tray = SystemTray.getSystemTray();

        //If the icon is a file
        final Image image = Toolkit.getDefaultToolkit().getImage(iconURL);

        TrayIcon trayIcon = new TrayIcon(image, "");
        //Let the system resize the image if needed
        trayIcon.setImageAutoSize(true);
        //Set tooltip text for the tray icon
        trayIcon.setToolTip(notificationTitle);
        tray.add(trayIcon);

        trayIcon.displayMessage(
                notificationTitle,
                notificationBody,
                (notificationTitle.contains("Fall") ? MessageType.ERROR : MessageType.WARNING)
        );
    }
}

Side note: Now that I am reviewing the code of this old project, I realise that despite me naming them so at the time, these are not interfaces. It would have probably been good to apply some actual interfaces for abstraction. How far I have come.

Threshold Exceeded Core Java Notification (Windows)

Threshold Fallen Core Java Notification (Windows)

Controller

In the Model-View-Controller (MVC) architecture, the Controller layer is responsible for processing user input and making decisions based on that input. In the Investment Notifier project, the Controller layer was the heart of the application.

The Controller layer in this project was responsible for parsing and processing the CLI parameters passed to the application. It took groups of 4 command line elements and manually populated either crypto or stock list data based on the prefix of the first element. Additionally, the Controller layer managed the interaction between the Model layer (which stored the data about the stocks and cryptocurrencies being monitored) and the View layer (which displayed the notifications to the user).

package com.dhi13man.investment_notifier;

import com.dhi13man.investment_notifier.interfaces.InvestmentTrackInterface;
import com.dhi13man.investment_notifier.models.Crypto;
import com.dhi13man.investment_notifier.models.Stock;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Make Lists of Cryptos and Stocks that you would like to be notified about.
        final List<Crypto> cryptos = new ArrayList<>();
        final List<Stock> stocks = new ArrayList<>();
         // Refresh price every 60 seconds to prevent API burnout.
        final long priceRefreshTime = 60000;

        // If not enough command line parameters, hard-coded investments will be tracked.
        if (args.length < 4) {
            // Add your Cryptos and Stocks here for manual usage.
            cryptos.add(
                new Crypto(
                    "ethereum",
                    "Ethereum",
                    180000,
                    220000
                )
            );
            stocks.add(new Stock("SBI", "SBI", 8, 10));
        }
        // Get Cryptos and Stocks from command line parameters.
        else {
          processCommandLineParameters(args, cryptos, stocks);
        }

        // Create the interface that schedules checking and notification of prices (process flow of the app)
        new InvestmentTrackInterface("inr", cryptos, stocks, priceRefreshTime);
    }

    /**
     * Processes the Command Line arguments to process Stocks and Cryptos
     * @param args Command line arguments
     * @param cryptos Crypto List that the application will us
     * @param stocks Stock list that the application will use
     */
    private static void processCommandLineParameters(String[] args, List<Crypto> cryptos, List<Stock> stocks) {
        final Logger logger = Logger.getLogger(Main.class.getName());
        int i;
        for (i = 0; i + 3 < args.length; i += 4) {
            if (args[i].startsWith("c_"))
                cryptos.add(
                    new Crypto(
                        args[i].substring(2),
                        args[i + 1],
                        Double.parseDouble(args[i + 2]),
                        Double.parseDouble(args[i + 3])
                    )
                );
            else if (args[i].startsWith("s_"))
                stocks.add(
                    new Stock(
                        args[i].substring(2),
                        args[i + 1],
                        Double.parseDouble(args[i + 2]),
                        Double.parseDouble(args[i + 3])
                    )
                );
            else {
                logger.log(Level.SEVERE, "Invalid Command Line Parameters. Every (4 * i + 1)th element must start with c_ or s_.");
                logger.log(Level.SEVERE, "Refer to README.MD");
            }
        }

        // If there are more command line parameters than needed.
        if (args.length != i) {
            logger.log(Level.SEVERE, "Invalid Command Line Parameters. Last {0} parameter(s) were ignored due to improper format!", (args.length - i));
            logger.log(Level.SEVERE, "Refer to README.MD");
        }
    }
}

One of the most important aspects of the Controller layer in the Investment Notifier project was its ability to handle errors and exceptions. Since the project relied heavily on external APIs and user input, it was crucial that the Controller layer was able to gracefully handle unexpected situations and provide clear error messages to the user.

Another important feature of the Controller layer was its ability to abstract class members to allow read-only access. This was important for ensuring the integrity of the data being stored in the Model layer and preventing accidental changes to that data.

Overall, the Controller layer in the Investment Notifier project played a critical role in processing user input, managing the data being monitored, and interacting with external APIs. It was a key component of the application’s overall architecture and was responsible for ensuring that everything worked smoothly and efficiently.

Business Logic

Then, I tackled the challenge of developing the business logic required to bring the idea into reality. E.g. Making network GET/POST requests to APIs using just core Java utilities. I had to handle the responses and make sure everything was working properly.

package com.dhi13man.investment_notifier.interfaces;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;

/**
 * Uses Core Java HttpsURLConnection to make network requests.
 */
public class NetworkRequestInterface {
    /** User agent required for making requests. **/
    private static final String USER_AGENT = "Mozilla/5.0";

    /** Logger object to log the class' outputs. **/
    private static final Logger logger = Logger.getLogger(NetworkRequestInterface.class.getName());

    /**
     * Gets the response of the connection after it has been made
     * @param connection [HttpsURLConnection]: The connection object with which the request is made.
     * @return response [StringBuilder]: The response received by the connection.
     **/
    private StringBuilder decipherResponse(HttpsURLConnection connection) throws IOException {
        BufferedReader in = new BufferedReader(
                new InputStreamReader(connection.getInputStream())
        );
        String inputLine;
        StringBuilder response = new StringBuilder();

        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        return response;
    }

    /**
     * Make a HTTPS GET request.
     * @param endpointURL URL of the Endpoint where the GET request is to be made.
     * @throws IOException as network request could have error.
     * @return the Response body (or code if failed) as String.
     **/
    public String requestGET(String endpointURL) throws IOException {
        URL obj = new URL(endpointURL);
        HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
        con.setRequestMethod("GET");
        con.setRequestProperty("User-Agent", USER_AGENT);
        con.setRequestProperty("accept", "application/json");
        int responseCode = con.getResponseCode();
        logger.log(Level.FINE, "GET Response Code: {0}", responseCode);
        StringBuilder response;
        if (responseCode == HttpsURLConnection.HTTP_OK) { // success
            response = decipherResponse(con);
        } else {
            response = new StringBuilder().append(responseCode);
            logger.log(Level.SEVERE, "GET request failed!");
        }
        return response.toString();
    }

    /**
     * Make a HTTPS POST request.
     * @param endpointURL URL of the Endpoint where the POST request is to be made.
     * @param postParameters the data to be sent as the POST request body
     * @throws IOException as network request could have error.
     * @return the Response Code of the POST request.
     **/
    public int requestPOST(String endpointURL, String postParameters) throws IOException {
        URL obj = new URL(endpointURL);
        HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("User-Agent", USER_AGENT);
        con.setRequestProperty("Content-Type", "application/json; utf-8");

        // For POST only - START
        con.setDoOutput(true);
        final OutputStream os = con.getOutputStream();
        os.write(postParameters.getBytes(StandardCharsets.UTF_8));
        os.flush();
        os.close();
        // For POST only - END

        int responseCode = con.getResponseCode();
        logger.log(Level.FINE, "POST Response Code: {0}", responseCode);

        if (responseCode == HttpsURLConnection.HTTP_OK) { //success
            decipherResponse(con);
        } else {
            logger.log(Level.SEVERE, "POST request failed!");
        }
        return responseCode;
    }
}

Throughout the process, I made sure to provide clear documentation in my Java code. I also learned about error throwing and handling, parsing and forming JSON without external libraries (which was a bit of a pain), abstracting class members to allow read-only access, Java string building, access modifiers, and more.

Conclusion

Overall, I’m really proud of what I was able to accomplish with the Investment Notifier. It may not be a production-level app, but it was a great way to learn more about Java and build something fun in the process. It was a testament that you don’t need complex frameworks or external libraries to create cool stuff. Sometimes all you need is a granddaddy like Java, and some core utilities provided with it.

If you’re a beginner programmer like me, I highly recommend taking on a simple project like this. It’s a great way to build your skills and gain more confidence in your abilities. And who knows — you might just end up creating something really cool.

Buy me a coffee at: https://www.buymeacoffee.com/dhi13man

dhi13man is developing Software, Apps, Libraries, Open Source Contributions etc. (buymeacoffee.com)

Full Source Code on GitHub

#java #cli #awt #cryptocurrency #stockmarketinvesting #developer #programming

0
Subscribe to my newsletter

Read articles from Dhiman Seal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dhiman Seal
Dhiman Seal

I have a Bachelor of Technology in Electronics and Communication Engineering from the NIT, Silchar and I am currently working as a Backend Software Engineer at Groww! I aim to help come up with scalable solutions to problems and work with teams that build industry-changing technology. Open Source gets me off and I also love to guide and teach in fields I am confident in. Check out my developer profiles for proof. ;) Reach out to me directly on LinkedIn or Twitter. 💬💬💬