Observer Pattern

Observer pattern is widely used in many real time applications. It is one of the behavioural design patterns. In this post we will try to understand it and try to answer questions around it.

Let’s understand it with an example of stock market.

Basic idea is to observe something and get notified based on some condition so that observer can take necessary action accordingly.

With following Java program it becomes a bit more cleared on how this design pattern fundamentally works.

class Stock {
    String name;

    double price;

    public Stock(String name, double price) {
        this.name = name;
        this.price = price;
    }
}

class StockMarket {

    private List<Stock> stocks;

    private List<StockObserver> observers;

    StockMarket() {
        observers = new ArrayList<>();

        stocks = new ArrayList<>();
        stocks.add(new Stock("Apple", 1000.23));
        stocks.add(new Stock("Google", 1000.23));
        stocks.add(new Stock("Microsoft", 1000.23));
        stocks.add(new Stock("Facebook", 1000.23));
        stocks.add(new Stock("Tesla", 1000.23));

    }

    public void start() throws InterruptedException {
        Random random = new Random();

        while(true) {
            Thread.sleep(1000);
            Stock stock = stocks.get(random.nextInt(stocks.size()));
            stock.price = random.nextDouble();
            observers.forEach(o -> o.movement(stock));
        }
    }

    public void register(StockObserver observer) {
        this.observers.add(observer);
    }

}

interface StockObserver {

    void movement(Stock stock);

}


class Trader implements StockObserver {

    @Override
    public void movement(Stock stock) {
        System.out.printf("%s stock price %f\n", stock.name, stock.price);
    }
}

Note that the StockMarket is completely unaware of the underlying observer. Any class can be a observer not necessarily it to be a Trader. However, every Trader will be an StockObserver by default.

Consider a scenario where a Trader is interested in specific stock and not all stock. We can think of two options here,

  1. StockMarket can register Trader for specific Stock.
  2. Trader can act on specific Stock and ignore the other movements.

Which option is more suitable here and why?

Preferred choice may be first one as it stops flow of unneccessary information to Observer which they are not interested in and saves function calls.

What is StockMarket is overloaded with lot of observers?

Considering the example, there can be lot of Traders observing the stock market and wants a real time information. For each and every stock the pattern used will not likely going to scale. It may work but it may end up with higher latency.

What if one of the Observer call fails!?

All the subsequent observers should get notifications. Otherwise we may need to perform check to see whether the observer is active or not before notifying. In above example, if any of the observer fails it will not notify remaining observers.

Should StockMarket be responsible for notifying observers?

That’s a valid question. It can be moved to separate class to filter out events and notify to respective observers.

This is pretty simple implementation of the observer pattern. We may need to look into other aspects of the system to deal with the issues arising in current design. For example, rather than immediately updating we can build a queue to fan out. But that’s not the solution in all use cases. We need to take a wholestic view of the business and system requirements into consideration to decide the possible approach.

Summary

The observer pattern is a behavioural pattern which can be used to notify one or more observers of the subject in case of any state change. It’s crutical to understand the scalability, performance concerns and heavy dependency on observer interface before adapting this pattern on a large scale use case.

Platform vs. Virtual Threads

This blog post is a raw comparison of platform and virtual threads. We will use both the types of threads and see how they behave and perform.

It is important to note that the execution time and other matrics may vary system to system, however, it can definitely give a rough idea about the overall picture. We will try to keep this comparison as fair as possible, if I fail to do so, feel free to post comment below.

My System

  • MacBook Pro
    • RAM 16 GB
    • Processor 2.6 GHz 6-Core Intel Core i7
  • Java 21

Time to start

Platform Threads : 21 ms

private static void platform() {
    long start = System.currentTimeMillis();
    for(int i = 0; i < 100; i++) {
        Thread t = new Thread(() -> System.out.println(Thread.currentThread().getThreadGroup().getName()));
        t.start();
    }
    long end = System.currentTimeMillis();
    System.out.println("Platform : " + (end - start) + " ms");
}

Virtual Threads : 18 ms

private static void virtual() {
    long start = System.currentTimeMillis();
    for(int i = 0; i < 100; i++) {
        Thread.startVirtualThread(() -> System.out.println(Thread.currentThread().getThreadGroup().getName()));
    }
    long end = System.currentTimeMillis();
    System.out.println("Virtual : " + (end - start) + " ms");
}

For 100 Threads the difference doesn’t look significant. However, If I increase the count of threads from 100 to 1000 the difference increases drastically.

  • Platform Threads : 235 ms
  • Virtual Threads : 29 ms

Reason

Even though the time taken to initialize threads are different, underlying operation would have taken same amount of time. Virtual threads enables better utilisation of the resources compared platform threads (virtual thread actually uses platform threads internally).

The major difference here is that the platform thread managed by OS are blocking, but virtual thread which are managed by JVM are non-blocking. So, in case of virtual thread it will wait for the platform thread to be available but won’t block the execution of the program.

Speed

Platform : 24 ms

private static void platform() {
    Thread t = new Thread(() -> {
        long start = System.currentTimeMillis();
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getThreadGroup().getName());
        }
        long end = System.currentTimeMillis();
        System.out.println("Platform : " + (end - start) + " ms");
    });
    t.start();
}

In case of virtual thread, there is no output in console. Looks like it didn’t print anything to console, the thread didn’t start.

private static void virtual() {
    Thread.startVirtualThread(() -> {
        long start = System.currentTimeMillis();
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getThreadGroup().getName());
        }
        long end = System.currentTimeMillis();
        System.out.println("Virtual : " + (end - start) + " ms");
    });
}

It seems we need to use join here.

private static void virtual() throws InterruptedException {
    Thread.startVirtualThread(() -> {
        long start = System.currentTimeMillis();
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getThreadGroup().getName());
        }
        long end = System.currentTimeMillis();
        System.out.println("Virtual : " + (end - start) + " ms");
    }).join();
}

And it worked, but took comparatively more time than platform thread, almost double.

Virtual : 49 ms

Let’s try to be fair here,

private static void platform() throws InterruptedException {
    Thread t = new Thread(() -> {
        long start = System.currentTimeMillis();
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getThreadGroup().getName());
        }
        long end = System.currentTimeMillis();
        System.out.println("Platform : " + (end - start) + " ms");
    });
    t.start();
    t.join();
}

Platform : 26 ms

For some reason, platform thread is winning here.

I thought of replacing System.out.println(Thread.currentThread().getThreadGroup().getName()); with System.out.println("."); to keep it simpler.

If we run both methods together from the main method,

public class Test {

    public static void main(String[] args) throws InterruptedException {
        platform();
        virtual();
    }

    private static void virtual() throws InterruptedException {
        Thread.startVirtualThread(() -> {
            long start = System.currentTimeMillis();
            for(int i = 0; i < 1000; i++) {
                System.out.println(".");
            }
            long end = System.currentTimeMillis();
            System.out.println("Virtual : " + (end - start) + " ms");
        }).join();
    }

    private static void platform() throws InterruptedException {
        Thread t = new Thread(() -> {
            long start = System.currentTimeMillis();
            for(int i = 0; i < 1000; i++) {
                System.out.println(".");
            }
            long end = System.currentTimeMillis();
            System.out.println("Platform : " + (end - start) + " ms");
        });
        t.start();
        t.join();
    }
}

Multiple executions,

  • Platform : 19 ms and Virtual : 15 ms
  • Platform : 21 ms and Virtual : 24 ms
  • Platform : 21 ms and Virtual : 16 ms
  • Platform : 22 ms and Virtual : 14 ms
  • Platform : 22 ms and Virtual : 29 ms

After changing the sequence of methods the result changes dramatically,

...
public static void main(String[] args) throws InterruptedException {
    virtual();
    platform();
}
...

Multiple executions,

  • Platform : 17 ms and Virtual : 60 ms
  • Platform : 7 ms and Virtual : 35 ms
  • Platform : 10 ms and Virtual : 47 ms
  • Platform : 9 ms and Virtual : 35 ms
  • Platform : 6 ms and Virtual : 30 ms

Okay, lot of confusion here. Let’s find answers of each one by one.

Reason

Why virtual thread couldn’t execute?

In first case the problem is that the main thread exits before the virtual thread completes it’s execution. We can definitely join the virtual thread to our main thread which we did. Again the non blocking behaviour made it behave this way.

public static void main(String[] args) throws InterruptedException {
    virtual();
    Thread.sleep(50000);
}

This gives Virtual : 33 ms

Why platform thread executed faster with join?

In case of virtual threads JVM has to deal with additional work underneath, which leads to slight delay in the execution. Even though it’s one thread and we are joining it to main, compared to platform thread it took more time to finish. This ovehead may include things like park/unpark the task based on JVM or CPU bound decision making.

How sequence of method is impacting the thread task execution time?

Again the blocking nature of platform thread makes life of virtual thread easier. For platform thread executed first the JVM, CPU or I/O is managed differently compared to virtual thread if executed first. Since the task is pretty simple the impact is significantly lower but such differences can make developer wonder if not understood properly.

JavaFX Project Setup

Introduction

Using JavaFX platform, we can build applications for desktop, mobile or embessed systems. It has large set of components to design rich user interface.

JavaFX allows,

  • designing the user interface interactively using Scene Builder.
  • testing the user inteface using TestFX.
  • styling the user interface with the help of CSS.

JavaFX historically used to part of Java installation, however, now it is a standalone component which works on top of JDK.

I faced multiple challenges during setting up the JavaFX with Java 21 to build a simple application recently. I would like to share the setup I ended up with which may help you as well.

We will be going to use following tools,

Here we will going to use Maven which will take care of downloading required modules, however, you can use JavaFX SDK as well.

Project Setup

IntelliJ bundles JavaFX plugin in it.

Create New Project in IntelliJ.

Select JavaFX in side bar. Select Project SDK as Java 21.

For now, we do not need any additional dependencies, we will just click on Finish.

This will take time finish setting up project and indexing.

Once it’s ready we can simply run the Main class which is HelloApplication.java in this case.

It should run successfully and show a window with a button “Hello!”.

As you can see some gibberish text is shown. You may see following logs in Console.

This looked like some issue with the fonts. So I had to add following line in main method of HelloApplication.java.

scene.getRoot().setStyle("-fx-font-family: 'serif'");

After running it again it appeared like following,

Changes in POM

If you go to Project Structure > Modules you will find many OpenFX dependencies are added.

If we look into pom.xml file, we can see following dependecies are add for JavaFX.

...
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-controls</artifactId>
    <version>11.0.2</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>11.0.2</version>
</dependency>
...

By default the selected version is 11.0.2 which we will going to change to recent version 22.

<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-controls</artifactId>
    <version>22</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>22</version>
</dependency>

However, the build started failing with module error.

I had no clue what could have went wrong, so I did clean up and try to build again but didn’t work.

Surprisingly, when I executed with command line using mvn it worked!

mvn clean compile
mvn javafx:run

Strangely, there is no error in module-info.java, from command line it worked well but failing when I run it from IntelliJ. I felt this issue is something specific to IntelliJ and not to JavaFX.

I found the similar problem is discussed on the forum.

I was using IntelliJ CE version shown in following image,

Moreover, importing the same project to IntelliJ Ultimate Edition worked miraculously without any modifications.

The javafx-maven-plugin is already on latest version, thus we are not changing anything here for now.

...
<plugin>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-maven-plugin</artifactId>
    <version>0.0.8</version>
    <executions>
        <execution>
            <!-- Default configuration for running with: mvn clean javafx:run -->
            <id>default-cli</id>
            <configuration>
                <mainClass>com.example.hellojavafx/com.example.hellojavafx.HelloApplication</mainClass>
                <launcher>app</launcher>
                <jlinkZipName>app</jlinkZipName>
                <jlinkImageName>app</jlinkImageName>
                <noManPages>true</noManPages>
                <stripDebug>true</stripDebug>
                <noHeaderFiles>true</noHeaderFiles>
            </configuration>
        </execution>
    </executions>
</plugin>
...

Initialisation Error

During build and trying some maven command, I ended up with following error,

Error occurred during initialization of boot layer
java.lang.module.FindException: Module com.example.hellojavafx not found

some articles were suggesting to validate Java and JavaFX versions but that wasn’t the issue. Strangely, deleting target directory and running the application worked as it is.

Also, while running through command for the first time you may get following error,

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project hello-javafx: Fatal error compiling: error: invalid target release: 0 

I noticed that in maven-compiler-plugin the source and target version was 0 which I changed to 21 to fix this issue.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>0</source>
        <target>0</target>
    </configuration>
</plugin>

Module Info

Apart from pom.xml, you will find one module-info.java shown below. Here we can see module dependency on javafx.controls and javafx.fxml is mentioned. Also, javafx.fxml needs to access controllers we will define in out package thus we are opening it.

module com.example.hellojavafx {
    requires javafx.controls;
    requires javafx.fxml;


    opens com.example.hellojavafx to javafx.fxml;
    exports com.example.hellojavafx;
}

Whenever you introduce new dependency which you want to be available in your package or you want to open your package to external dependency. Make sure to declare it in module-info.java file.

Executable JAR

To make a runnable .jar I used maven-shade-plugin.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>com.example.hellojavafx.HelloApplication</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

I simple ran mvn clean package to create the jar file. However when I ran the jar with following command.

java -jar target/hello-javafx-1.0-SNAPSHOT.jar

It gave me following error,

Error: JavaFX runtime components are missing, and are required to run this application

To solve this, I had to declare an entry point class which can point to my Application class.

package com.example.hellojavafx;

public class EntryPoint {

    public static void main(String[] args) {
        HelloApplication.main(args);
    }
}

This new class I had to use in plugin instead of HelloApplication.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>com.example.hellojavafx.EntryPoint</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

And this time it worked! (with warning)

➜  hello-javafx java -jar target/hello-javafx-1.0-SNAPSHOT.jar
Aug 10, 2024 10:35:43 PM com.sun.javafx.application.PlatformImpl startup
WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @673ba40c'

The shade plugin adds everything to the classpath and since JavaFX relies on modules this setup breaks the execution. Thus, as a workaround we introduce another class which can be an entry point to the execution instead of JavaFX starter application.

Basically, if JavaFX application is loaded through classpath it actually breaks the encapsulation provided by modular approach. Here is the ticket for this warning.

Base64 Encoder

Base64 is popular system that transforms the binary data into limited set of 64 characters.

For example, text abc can be represented in binary as 011000010110001001100011, Base64 divides this binay data into group of 6 bits 011000 010110 001001 100011 and converts it to as per the encoding table.

Base64 Encoding Table has total 64 (2^6) characters.

  • 0 to 25 : A to Z
  • 26 to 51 : a to z
  • 52 to 61 : 0 to 9
  • 62 : Plus symbol ‘+’
  • 63 : Forward slash ‘/’

Additional symbo ‘=’ is used for padding in case of bits are not in multiple of 6.

According to encoding table, 011000 010110 001001 100011 which is 24 (011000) 22 (010110) 9 (001001) 35 (100011) converted to YWJj in Base64 format.

public class Encoder {

    private static final Encoder ENCODER = new Encoder();

    private static final int ENCODE_BASE = 6;

    private final char[] encodingTable = new char[64];

    private Encoder() {
        initEncodingTable();
    }

    public static Encoder getInstance() {
        return ENCODER;
    }

    public String encode(String input) {
        int[] binary = toBinary(input);
        StringBuilder encoded = new StringBuilder();

        for (int start = 0; start < binary.length; start += ENCODE_BASE) {
            int end = Math.min(start + ENCODE_BASE, binary.length - 1);

            int power = ENCODE_BASE - 1;
            int charIndex = 0;
            for (int j = start; j < end; j++) {
                charIndex += (binary[j] * Math.pow(2, power--));
            }

            encoded.append(encodingTable[charIndex]);
        }

        int pad = binary.length % ENCODE_BASE;
        encoded.append("=".repeat(pad));
        return encoded.toString();
    }

    private int[] toBinary(String input) {
        final int bits = 8;
        int[] binary = new int[input.length() * bits];

        try {
            byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
            int index = 0;
            int max = (int) Math.pow(2, (bits - 1));

            for (byte b : bytes) {
                int val = b;
                for (int i = 0; i < bits; i++) {
                    binary[index++] = ((val & max) == 0 ? 0 : 1);
                    val <<= 1;
                }
            }
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
        return binary;
    }

    private void initEncodingTable() {
        int index = 0;

        for (char i = 'A'; i <= 'Z'; i++)
            encodingTable[index++] = i;

        for (char i = 'a'; i <= 'z'; i++)
            encodingTable[index++] = i;

        for (char i = '0'; i <= '9'; i++)
            encodingTable[index++] = i;

        encodingTable[62] = '+';
        encodingTable[63] = '/';
    }
}

This program is implemented to make it more easier to understand. However, for optimal approach we can look into java.util.Base64 class which is bit complex to understand and considers much bigger character set and scenarios than the implemented one.

5 mistakes I did as a Software Developer

Undoubtedly, mistakes are part of everyone's life. The challenge is to learn from the mistakes and avoid repeating them in future. 

As a programmer, you will gradually improve in career by contributing to different projects, coding contests, pair programming etc. But the major aspect of improvement lies in learning from the mistakes.

Today I will talk about 5 mistakes that I did as Software Developer that I could have avoided.

1. Try to focus more on solution rather than problem

This mistake is not only applies to coding but in general to any problem of our lives. We are so desperately invested in the solution that we forget about the problem or we do not pay enough attention to the problem.

I learned that the more we focus on problem the better solution we can deliver, that will not just give good value to the customer but also a deep and clear insight into the solution as well.

2. Deliver things without enough Test Cases

I have underrated the value of unit testing and especially TDD for a long time. The confidence you get on your work comes from enough and solid test cases, is imppacable. 

The value you build is not just immediate for your confirmation or avoiding issues in your delivery but this is for all those developers writing the code which may impact your changes and can confidently make changes in the same code because of efforts you put in for writing required test cases.

3. Waiting for "good" project to contribute

There is nothing like good or bad project that I learned over course of time. It's a problem and solution cycle for every software developer which we keep on focusing for. If you are looking for a good project or good company, you may wait for longer. 

To learn and experiment anything, do not wait for project to come to you that's a reactive approach, go ahead and create something from the technology which interests you.

4. Shy away from learning from Seniors

You will find many mentors in your career, try to gain as much knowlege you can gain from them. You need to be proactively discuss solutions and understanding with them. This will give you a different perspective and opportunity to align your thought process with the experienced folks.

You would argue that you can learn from courses, youtube or chatgpt. Well you should and you woul learn from the internet but the conversations you will do with your mentors can not be replaced with internet for sure that I learned gradually. 

5. Hesitate to ask questions

I always stopped myself from asking the questions during discussions and meetings. Many times the question came to my mind was asked by someone else, and I regreted. Asking questions during presentations, pair programming, team meetings etc. rewards you with many things. Your team understands that you are interested to learn, you get required clarity, you gain confidence, the facilitator feels that you are engaged into the conversation so and so forth. 

However, on contrary if you don't ask, you mainly miss the easy opportunity to practice courage. When your inner  self is resisting and still you ask the question, see how it feels. Moreoever, you missed an opportunity of curiously seeking ansers from the learned ones which you may not easily get answer of, on your own.


There are tons of other mistakes, but these 5 I believe could have turned me in a different direction initially, and those who are going to be software developers in the future may learn from them and avoid them as much as possible for better growth and satisfaction.