Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Sunday, April 26, 2026

Spring WebClient Failed to Resolve Example

Have you ever encountered Spring's WebClient throwing a "failed to resolve 'hostname' after x queries" error?. Well, if you ran my Crowsnest project prior to my latest merge, then chances are you may have. Especially, if you ran it inside your internal network. Here's what the error looks like.

Spring WebClient Failed to Resolve Cause

As the error states, the target hostname can't be resolved. It is a DNS thing. By default WebClient uses the JVM implementation to resolve the hostname. In my case, my Linux box was inside a VPN and somehow Java's standard networking library (e.g. java.net.InetAddress) was ignoring the local DNS settings. Worked fine when the target was accessible in the Internet.

Spring WebClient Failed to Resolve Sorted

To solve this dilemma, we simple change the default hostname resolver to something that can do the job. Luckily, there is Netty's DNS resolver we can use. It is io.netty.resolver.DefaultAddressResolverGroup, and we just tell Spring to use it. So WebClient will now use Netty's DNS Resovler thru HttpClient and no longer the default JVM implementation. We'll need to change a couple of things in our code.

Create a new WebClient that will be injected in other parts of the code.


package net.codesamples.crowsnest;

// ... other imports snipped
import io.netty.resolver.DefaultAddressResolverGroup;
import reactor.netty.http.client.HttpClient;

@SpringBootApplication
@EnableScheduling
public class CrowsnestApplication {

	public static void main(String[] args) {
		SpringApplication.run(CrowsnestApplication.class, args);
	}

	@Bean
	public WebClient webClient() {
		HttpClient httpClient = HttpClient.create().resolver(DefaultAddressResolverGroup.INSTANCE);
		return WebClient.builder()
				.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
	}
}

Use the bean in other parts of the code thru @Autowired.


package net.codesamples.crowsnest;

// ... impoerts snipped

@Component
public class ScheduledPings {
    private static final Logger log = LoggerFactory.getLogger(ScheduledPings.class);

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Autowired
    EnvironmentConfigurationWatcher environmentConfigurationWatcher;

    @Autowired
    SimpMessagingTemplate simpMessagingTemplate;

    @Autowired
    private WebClient webClient;

    private List internalEnvironmentList = new ArrayList<>();

    private ObjectMapper mapper = new ObjectMapper();

    @Scheduled(cron = "${cron.expression}")
    public void pinger() {
        log.info("The time is now {}", dateFormat.format(new Date()));
        List environmentList = environmentConfigurationWatcher.getEnvironmentList();
        log.info("Environment list: {}", environmentList);

        for (Environment environment : environmentList) {
            for (App app : environment.getApps()) {
                Mono mono = webClient.get()
                        .uri(app.getUrl())
                        .exchangeToMono(clientResponse -> {
                            HttpStatusCode statusCode = clientResponse.statusCode();
                            if (statusCode.is2xxSuccessful()) {
                                app.setStatus("up");
                            }
                            log.info("StatusCode: {} = {}", statusCode, app.getUrl());
                            return clientResponse.bodyToMono(String.class);
                        })
                        .onErrorResume(Exception.class, exception -> {
                            log.info("Exception {}", exception.getMessage());
                            app.setStatus("down");
                            return Mono.empty();
                        });
                mono.subscribe();
            }
        }

// ... code snipped
}

Spring WebClient Failed to Resolve Fixed

Things should now work again. If it does not, let me know.

I need more WebClients...

Friday, March 6, 2026

Deploy to Kubernetes Example

Alright, now that you got a docker image in the previous example, Dockerize Spring Boot App Example, what now? The most obvious next step is to spin up the docker image in a Kubernetes cluster. But GKE (Google Kubernetes Engine) is bloody expensive for us mere mortals. Luckily we can practice Kubernetes stuff with Docker Desktop.

Assumptions

I'm assuming you have read my previous example, Dockerize Spring Boot App Example, So you should have the following already set up.

  • Docker Desktop 4.55.0
  • Windows 11 Home 10.0.26200
  • IntelliJ IDEA 2023.3.4 (Community Edition)
  • OpenJDK 17.0.10
  • Crowsnest, example Spring Boot App
  • Lens 2025.6.261308-latest

Docker Desktop

First up is to start a Kubernetes cluster via Docker Desktop. Should just be a bunch of clicks. I just chose a single node cluster (Kubeadm). You should have something like below:

And if you have Lens K8S IDE. You'll have something like below.

Deploy to Kubernetes

First, let's check our image (docker images). Like so:


C:\workspace>docker images
                                                                                                    i Info →   U  In Use
IMAGE                                                                   ID             DISK USAGE   CONTENT SIZE   EXTRA
crowsnest:v1.0.0                                                        b9bfb190828e        567MB          197MB    U

Let's make sure we deploy to docker desktop kubernetes.


C:\workspace>kubectl config use-context docker-desktop
Switched to context "docker-desktop".

And the fun starts, create a deployment.


C:\workspace>kubectl create deployment crowsnest --image=crowsnest:v1.0.0
deployment.apps/crowsnest created

After deployment, take a look at Lens and Docker Desktop.

Or if you like CLI.


C:\workspace>kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
crowsnest-f56bb85ff-t9dn9   1/1     Running   0          28m

Well and good so far. But our web app is not reachable from the browser yet. We need to expose the port of the pod so we can get to the web app. Do note that your pod name might be different from this example.


C:\workspace>kubectl expose pod crowsnest-f56bb85ff-t9dn9 --port=8080 --name=crowsnest --type=LoadBalancer
service/crowsnest exposed

C:\workspace>kubectl get services
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
crowsnest    LoadBalancer   10.100.27.69   localhost     8080:31912/TCP   97s
kubernetes   ClusterIP      10.96.0.1              443/TCP          4d3h

You should have something like below.

Perfecto! We should be able to access Crowsnest on the browser now. localhost:8080.

Thank you Docker Desktop for helping us save money by practicing kubectl stuff with you, instead of the expensive GKE. Deploying to GKE should be similar. Just need to point kubectl to GKE.

Undeploy from Kubernetes

Most importantly when doing this on a paid Kubernestes service, don't forget to delete the pod, service, deployment, etc. GKE billing is astronomical (delete the project too to be sure!) Here are the commands to undo what you've done.


C:\workspace>kubectl delete service crowsnest --now
service "crowsnest" deleted from default namespace

C:\workspace>kubectl delete deployment crowsnest --now
deployment.apps "crowsnest" deleted from default namespace

C:\workspace>kubectl get services
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1            443/TCP   4d3h

C:\workspace>kubectl get pods
No resources found in default namespace.

Deploy to Kubernetes Wrap Up

Yes, we did all of the above steps manually. Yes, this can all be automated. But before we automate stuff, we have to do it manually. For example, you can create a GitHub action workflow to do all of the above steps to spin up your docker image to GKE, just with a yaml file. That would be another story. Happy spinning!

Learn more k8s...

Friday, February 20, 2026

Dockerize Spring Boot App Example

One of the benefits of a dockerize app (dockerize is synonymous with containerization, just like googling is to web searching) is the ease of deployment to different environments. Once you got that docker image built, you can stick it in any environment you like. Shall we dockerize your Spring Boot app? Here's a quick example.

Tools

Those are the tools I used to build this example.

Dockerfile

Before we can run the docker file, let's build the project first. Run, mvn clean install in IntelliJ or via command line. That should create a target directory. Now, let's take a look at the Dockerfile found at the root directory.


FROM eclipse-temurin:17-jdk-alpine
RUN addgroup -S crowsnest && adduser -S crowsnest -G crowsnest
USER crowsnest:crowsnest
COPY docker-files/ app-files
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} crowsnest.jar
ENTRYPOINT ["java", "-jar", "crowsnest.jar", "--spring.config.location=/app-files/application.properties"]

What does above file mean? What will it do? FROM means that this is the base image. Our crowsnest image will extend from this image. Docker Hub contains a lot of Docker images that are suitable as base images. In this case our base image is built by Eclipse Temurin loaded with OpenJDK 17.

The RUN command will execute the Alpine Linux shell commands addgroup and adduser. The end result is a user called crowsnest belonging to the crowsnest group. This user will run the Crowsnest Spring Boot app. We don't want root running the app. The -S is a flag for system services or daemon group.

The USER command will set the user name and user group to crowsnest as the default user and group. This user is used for succeeding RUN, ENTRYPOINT, and CMD commands.

The COPY command, as it says, will copy the files in our local docker-files directory to the app-files directroy in the Alpine Linux image.

The ARG command defines the JAR_FILE variable that is passed at build time to the builder.

The next COPY command, copies all the jar files from the target directory into a single jar called crowsnest.jar.

Lastly ENTRYPOINT command configures the container to run as an executable. In this case, it will run java with the -jar and --spring.config.location parameters.

Right, let's build the image using the docker build command. Like so:


C:\workspace\crowsnest>docker build -t crowsnest:v1.0.0 .
[+] Building 5.2s (8/8) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile

The above command builds the crowsnest image with the image identifier (i.e tag) crowsnest:v1.0.0 on the current path. I sometimes add the option --no-cache to not use cache when building the image. After a successful build, you should have something like below. Old school way is docker images on the CLI (Command Line Interface.

Running the Docker Image

Righto, let's see this image in action. Go to the command line and run docker run -p 8080:8080 crowsnest:v1.0.0. This command will create and run a new container from the image tagged crowsnest:v1.0.0 exposing port 8080 and routing incoming requests to Crowsnest running on port 8080. In the words, the first 8080 is the container port number. The second is your app's port number. You should have something like below.


C:\workspace\crowsnest>docker run -p 8080:8080 crowsnest:v1.0.0

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.9)

2026-02-18T15:26:52.406Z  INFO 1 --- [crowsnest] [           main] n.c.crowsnest.CrowsnestApplication       : Starting CrowsnestApplication v0.0.1-SNAPSHOT using Java 17.0.17 with PID 1 (/crowsnest.jar started by crowsnest in /)
2026-02-18T15:26:52.409Z  INFO 1 --- [crowsnest] [           main] n.c.crowsnest.CrowsnestApplication       : No active profile set, falling back to 1 default profile: "default"
2026-02-18T15:26:53.293Z  INFO 1 --- [crowsnest] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2026-02-18T15:26:53.307Z  INFO 1 --- [crowsnest] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2026-02-18T15:26:53.307Z  INFO 1 --- [crowsnest] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.50]
2026-02-18T15:26:53.343Z  INFO 1 --- [crowsnest] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2026-02-18T15:26:53.344Z  INFO 1 --- [crowsnest] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 878 ms
2026-02-18T15:26:53.796Z  INFO 1 --- [crowsnest] [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page: class path resource [static/index.html]
2026-02-18T15:26:53.974Z  INFO 1 --- [crowsnest] [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : Starting...

If you are not old school, the docker desktop UI will look like so:

You can start/stop the image from running via the Actions button. Chances are you might experience some connectivity problems. Can't connect to an API or database. Click on the three dots then "Open in terminal". This should give you a shell prompt like below. As you can see, we are running Alpine Linux. Remember FROM eclipse-temurin:17-jdk-alpine? On the root directory is app-files and crowsnest.jar. Built with the following commands COPY docker-files/ app-files and COPY ${JAR_FILE} crowsnest.jar. Here, you can try ping, curl, wget, telnet, etc. to check connectivity. Excellent!

Dockerize a Spring Boot App

Outstanding. We have built and ran a docker image. We can spin this image up in any environment we like. To recap, we build our image from a base image then add what we need like the necessary configuration files, the application itself, etc. Finally, we specify our app as the default executable. Happy dockerizing!

Docker some more...

Saturday, February 7, 2026

Spring SimpMessagingTemplate Example

Want to send notifications to your WebSocket clients? What if you want to send messages to connected clients so they can update their UI? You definitely can send messages from the back-end (e.g. from any part of your application). Any application component can send messages to the message broker. The easiest way to do so is to inject a SimpMessagingTemplate and use it to send messages. Typically, you would inject it by type. If another bean of the same type exists, qualify it by name (@Qualifier).

My example is taken from my simple monitoring app, Crowsnest (Crowsnest repository in GitHub). Here's the code where I use SimpMessagingTemplate. This web app just checks whether sites are online or offline.


package net.codesamples.crowsnest;

... imports snipped ...

@Component
public class ScheduledPings {
    private static final Logger log = LoggerFactory.getLogger(ScheduledPings.class);

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Autowired
    EnvironmentConfigurationWatcher environmentConfigurationWatcher;

    @Autowired
    SimpMessagingTemplate simpMessagingTemplate;

    private WebClient webClient = WebClient.create();

    private List internalEnvironmentList = new ArrayList<>();

    private ObjectMapper mapper = new ObjectMapper();

    @Scheduled(cron = "${cron.expression}")
    public void pinger() {
        log.info("The time is now {}", dateFormat.format(new Date()));
        List environmentList = environmentConfigurationWatcher.getEnvironmentList();
        log.info("Environment list: {}", environmentList);

        for (Environment environment : environmentList) {
            for (App app : environment.getApps()) {
                Mono mono = webClient.get()
                        .uri(app.getUrl())
                        .exchangeToMono(clientResponse -> {
                            HttpStatusCode statusCode = clientResponse.statusCode();
                            if (statusCode.is2xxSuccessful()) {
                                app.setStatus("up");
                            }
                            log.info("StatusCode: {} = {}", statusCode, app.getUrl());
                            return clientResponse.bodyToMono(String.class);
                        })
                        .onErrorResume(Exception.class, exception -> {
                            log.info("Exception {}", exception.getMessage());
                            app.setStatus("down");
                            return Mono.empty();
                        });
                mono.subscribe();
            }
        }

        if (internalEnvironmentList.isEmpty()) {
            internalEnvironmentList = deepCopy(environmentList);
            simpMessagingTemplate.convertAndSend("/topic/environments", internalEnvironmentList);
        } else {
            if (!areTheSame(environmentList, internalEnvironmentList)) {
                log.info("environmentList NOT equal to internalEnvironmentList: \n {} \n {}",
                        environmentList, internalEnvironmentList);
                internalEnvironmentList = deepCopy(environmentList);
                simpMessagingTemplate.convertAndSend("/topic/environments", internalEnvironmentList);
            } else {
                log.info("environmentList equal to internalEnvironmentList");
            }
        }
    }

    private List deepCopy(List src) {
	    ... snipped ...
    }

    private boolean areTheSame(List list1, List list2) {
        ... snipped ...
    }
}

Straight to the point and no lollygagging, on lines 15, 52, and 58 are where the magic happens. So we let Spring inject the SimpMessagingTemplate, then send the message to the target destination. In this example, the destination is "/topic/environments". This is mapped to a method in a controller class. And that is how you update your connected clients via WebSocket.

In this example, on initial load where the environment list is still empty, we update all connected clients. The clients are updated as well when there is a change in the environment list. Here easily validated by checking if the old list matches the new list.

For setting up WebSocket on your Spring Boot app, front-end and back-end basics, I'll point you to my previous blog, Spring Boot WebSocket Example

Demonstration

I'm using IntelliJ IDEA 2023.3.4 (Community Edition). You can use any IDE you like. You can even go command line! Start up the Crowsnest web app. Once it has started, it should be accessible on http://localhost:8080 and you should have something like below when connected. Go ahead, click the connect button. Pay attention to the web console logs. Notice the JSON environments list.

Now, let's edit the environments.json file to force the back-end to notify the clients. For this, I'm just going to add "New" to the title. From "Development" to "New Development". On the back-end logs. The old environment list will not be equal to the new environment list which will trigger a publish message to the topic. You should see something like this on the logs.

Alright, we got a new title. Let's head back to the browser. It should now render the new title and you'll also see it log the new environment list. Like so.

Spring SimpMessagingTemplate

Now you don't need to poll the back-end incessantly. All you have to do is connect via WebSocket and wait for any notification. That should relieve the front-end of keeping up to date. Thank you for reading and happy WebSocket-ing.

More details here...

Friday, January 30, 2026

Java WatchService Example

Have you ever wanted to update some configuration of your web application without needing to restart it? Here is one way of doing it. Java's WatchService API allows you to monitor directoy for any changes. Without further ado, here's the example code.


package net.codesamples.crowsnest;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Component
public class EnvironmentConfigurationWatcher {

    private static final Logger log = LoggerFactory.getLogger(EnvironmentConfigurationWatcher.class);

    List environmentList = new ArrayList<>();

    private final String environmentsConfigFile = "environments.json";

    @Value("${watch.directory}")
    private String watchDirectory;

    @EventListener(ApplicationReadyEvent.class)
    public void startWatcher() {
        readConfig();

        new Thread(() -> {
            final Path path = FileSystems.getDefault().getPath(watchDirectory);
            try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
//            final WatchKey watchKey =
                path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
                while (true) {
                    final WatchKey wk = watchService.take();
                    for (WatchEvent event : wk.pollEvents()) {
                        //we only register "ENTRY_MODIFY" so the context is always a Path.
                        final Path changed = (Path) event.context();
                        if (changed.endsWith(environmentsConfigFile)) {
                            readConfig();
                        }
                    }
                    // reset the key
                    boolean valid = wk.reset();
                    if (!valid) {
                        log.info("Key has been unregistered");
                    }
                }
            } catch (IOException | InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }

    private void readConfig() {
        ObjectMapper mapper = new ObjectMapper();
        File envConfigFile = new File(watchDirectory + "/" + environmentsConfigFile);

        try (InputStream is = new FileInputStream(envConfigFile); ) {
            Environment[] environments = mapper.readValue(is, Environment[].class);

            environmentList.clear();
            environmentList.addAll(Arrays.asList(environments));
            log.info("Envs updated, {}", environmentList);
        } catch (Exception e) {
            log.error("Unable to read envs config file, {}", e.getMessage());
        }
    }

    public List getEnvironmentList() {
        return environmentList;
    }
}

The above code is part of my Crowsnest repository in GitHub. Crowsnest is a simple monitoring app. It just checks whether sites are online or offline. For this project, I didn't want to restart the web app everytime I update the environments to monitor as specified in the environments.json file.

To solve that requirement, I utilized Java's WatchService API. Starting on line 37, I specify the directory where the JSON file is located. Then create a new WatchService. Next is to register it to the Path we want to watch. I then specified the kind of event I want to monitor, ENTRY_MODIFY, this mean I'll be notified when a directory entry is modified.

All good so far? Right, take() is next. Which means this method waits until there is a key in the queue. So this is blocking. Once a key is available, I get a list of events from pollEvents(). The file name is stored as the context of the event, hence the call to context(). If it is the environment configuration file that has changed the trigger a read and rebuild environment list.

After polling the events, I invoke a reset so the key can go back to a ready state. This is crucial. Fail to invoke a reset and the key will not receive any further events.

Demonstration

On startup, you'll see something like below. I'm using IntelliJ IDEA 2023.3.4 (Community Edition). You can use any IDE you like. You can even go command line!

Now, let's edit the environments.json file. You should see some log output like below. Notice that I changed Developmental to Development.

Java WatchService

Pretty cool, huh?. We no longer need to restart our web app! Thanks WatchService API. Thank you for reading and happy coding.

See more...

Monday, May 19, 2025

GCP PubSub Receive Example

This is part two of the GCP (Google Cloud Platform) PubSub example. Part one is GCP PubSub Send Example. In part one, we learned how to set up GCP admin stuff (e.g. billing), GCP PubSub (e.g. topic), Google Cloud SDK Shell, and a lot more. So if you want to know how to make this example work, please read part one first. I am going straight into the GCP PubSub receive code and will not be discussing about how things are set up. That's covered in part one, please do have a read.

Tools and Materials

These are the tools I used to make this example:

  • IntelliJ IDEA 2023.3.4 (Community Edition)
  • openjdk-17.0.10
  • Spring Boot v3.4.5
  • Windows 11
  • Google account
  • Google Cloud SDK Shell
  • Git Bash

The example code is here, github.com/jpllosa/gcp-pubsub. You may grab a copy of it.

Spring Boot GCP PubSub Receive Code

As with part one, GCP PubSub Send Example. There are no changes to our pom.xml. It will have the same dependencies.

  • Spring Web (<artifactId>spring-boot-starter-web</artifactId>) - Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
  • Spring Integration (<artifactId>spring-boot-starter-integration</artifactId>, <artifactId>spring-integration-http</artifactId>) - Adds support for Enterprise Integration Patterns. Enables lightweight messaging and supports integration with external systems via declarative adapters.
  • Google Cloud Messaging (<artifactId>spring-cloud-gcp-starter-pubsub</artifactId>) - Adds the Google Cloud Support entry and all the required dependencies so that the Google Cloud Pub/Sub integration work out of the box.

Our GCP PubSub message receive code is short and sweet. Just like the send code. We subscribe to a GCP PubSub topic and when a message is receive, we print it out to the console. Here's the code. Explanation follows after.

  
package com.blogspot.jpllosa;

// imports snipped...

@SpringBootApplication
@RestController
public class GcpPubsubApplication {

// some send code snipped...

	@PostMapping("/postMessage")
	public RedirectView postMessage(@RequestParam("message") String message) {
		this.messagingGateway.sendToGcpPubsub(message);
		return new RedirectView("/");
	}

	// Receive
	@Bean
	public MessageChannel gcpPubsubInputChannel() {
		return new DirectChannel();
	}

	@Bean
	public PubSubInboundChannelAdapter messageChannelAdapter(
			@Qualifier("gcpPubsubInputChannel") MessageChannel inputChannel,
			PubSubTemplate pubSubTemplate) {
		PubSubInboundChannelAdapter adapter =
				new PubSubInboundChannelAdapter(pubSubTemplate, "gcp-pubsub-example-sub");
		adapter.setOutputChannel(inputChannel);

		return adapter;
	}

	@ServiceActivator(inputChannel = "gcpPubsubInputChannel")
	public void messageReceiver(String message) {
		System.out.println("Message received: " + message);
	}

}
  

The same with part one, only the GCP PubSub receive bits will be explained. Incoming messages will arrive on the MessageChannel (DirectChannel) we created. Simliar to sending, we have an inbound channel adapter that receives messages from GCP PubSub and forwards it to gcpPubsubInputChannel. The inbound channel adapter is linked to the GCP PubSub subscription name. The adapter is bound and listens for new messages from GCP PubSub gcp-pubsub-example-sub subscription. The method annotated with @ServiceActivator is called when new messages arrive at gcpPubsubInputChannel and we simply print the message on the console. Short and sweet.

Spring Boot GCP PubSub Receive in Action

Let's see our code in action. Run the Spring Boot app (e.g. mvnw spring-boot:run) or thru IntelliJ. When the app is ready, hit the /postMessage endpoint and you should have something like below printed on the console.

  
$ curl --data "message=GCP Pubsub send and receive!" localhost:8080/postMessage
  

We won't be able to see any messages in the GCP PubSub messages section because this happens very quick. If you want to see the messages sent on the messages tab, click PULL to delay the message being consumed. The delay is about 5-ish seconds. It would look like the below.

Spring Boot GCP PubSub Receive Wrap Up

There you have it. Your travel to GCP PubSub has come full circle, assuming you read part one of course. As per usual, we give thanks to Spring for making it easy for us by abstracting whatever it is happening under the hood. Alternatively, you can use other client libaries to connect to GCP PubSub. We created an input channel and attached it to a channel adapter that has subscribed to GCP PubSub. Viola and that is all there is to it! Thank you for reading.

Monday, May 12, 2025

GCP PubSub Send Example

If you are working on Shopify and you need to monitor webhook events (e.g. an order has been created), chances are you'll be directed to use Google Pub/Sub messaging service. In this blog, I'll show you how to setup PubSub on the Google Cloud Platform (GCP), then send a message and check the message on GCP PubSub cloud console.

Tools and Materials

These are the tools I used to make this example:

  • IntelliJ IDEA 2023.3.4 (Community Edition)
  • openjdk-17.0.10
  • Spring Boot v3.4.5
  • Windows 11
  • Google account
  • Google Cloud SDK Shell
  • Git Bash

The example code is here, github.com/jpllosa/gcp-pubsub. You may grab a copy of it.

GCP PubSub Setup

On GCP, create a new project. Like so.

Assign your new project with billing. As of this writing, Google provides free credits to new customers and free usage limits. For Google PubSub, it is 10 GB of messages per month. Please verify for yourselves if this is still the case. Here's what creating a billing account looks like and it being connected to the project ID.

Now that the administrative stuff has all been set up, it's time to create a PubSub topic and subscription.

Google Cloud SDK Shell Setup

Install and initialize the Google Cloud SDK. For installing the Google Cloud SDK Shell, it is better to get it from the horses' mouth. Please follow the Google Cloud documentation on how to install and initialiaze it for your project. Once initialized, enable GCP PubSub. Here are a few checkpoints so you'll know you are heading in the right direction. First and foremost, you must log in with application default credentials using the following command, gcloud auth application-default login. When you are logged, you should have something like below.

Next is to set the shell to your project, gcloud config set project <PROJECT-NAME>. Here's the command to see the available projects, gcloud config projects list. When you have done those, you should have something like below.

Lastly, enable GCP PubSub, gcloud services enable pubsub.googleapis.com. Output would look like below.

For Windows, we can verify that our Google credentials are saved by looking under C:\Users\username\AppData\Roaming\gcloud\application_default_credentials.json. This file should exist.

Spring Boot GCP PubSub Send Code

I used Spring Initialzr to create the skeleton project. The following are the dependencies we need:

  • Spring Web - Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
  • Spring Integration - Adds support for Enterprise Integration Patterns. Enables lightweight messaging and supports integration with external systems via declarative adapters.
  • Google Cloud Messaging - Adds the Google Cloud Support entry and all the required dependencies so that the Google Cloud Pub/Sub integration work out of the box.

Our pom.xml should at least have the following dependencies.

  
<!-- snipped -->

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>com.google.cloud</groupId>
	<artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.integration</groupId>
	<artifactId>spring-integration-http</artifactId>
</dependency>

<!-- snipped -->
  

Our GCP PubSub message sender code is short and sweet. When an HTTP POST is received, it then submits the message to GCP PubSub. Here's the code. Explanation follows after.

  
package com.blogspot.jpllosa;

// imports snipped...

@SpringBootApplication
@RestController
public class GcpPubsubApplication {

	public static void main(String[] args) {
		SpringApplication.run(GcpPubsubApplication.class, args);
	}

	@MessagingGateway(defaultRequestChannel = "gcpPubsubOutputChannel")
	public interface GcpPubsubOutboundGateway {
		void sendToGcpPubsub(String text);
	}

	@Bean
	@ServiceActivator(inputChannel = "gcpPubsubOutputChannel")
	public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
		return new PubSubMessageHandler(pubsubTemplate, "gcp-pubsub-example");
	}

	@Autowired
	private GcpPubsubOutboundGateway messagingGateway;

	@PostMapping("/postMessage")
	public RedirectView postMessage(@RequestParam("message") String message) {
		this.messagingGateway.sendToGcpPubsub(message);
		return new RedirectView("/");
	}

}
  

Since this is a blog about GCP PubSub, I'll focus on explaining the code related to it. I won't be talking about the other annotations (e.g. @SpringBootApplication, @PostMapping, etc.). For example, @PostMapping is related to web and nothing to do with GCP PubSub. We just need that endpoint to trigger a send message to GCP PubSub. We can use other client libraries to communicate with GCP PubSub, not just Spring.

We use Spring Integration messaging gateway to write to a channel, gcpPubsubOutputChannel. This channel is still in Spring land. When a message is in this channel, it is then consumed by the MessageHandler (outbound channel adapter) which in turn publishes the message to GCP PubSub. The outbound channel adapter converts the Spring message into a GCP PubSub message and is published to the topic, gcp-pubsub-example. The @ServiceActivator annotation causes this MessageHandler to be applied to any new messages in inputChannel. Lastly, we got a REST endpoint listening for messages which then forward the message to GCP PubSub. Short and sweet.

Spring Boot GCP PubSub Send in Action

Alrighty, run the Spring Boot app. We can do it command line (e.g. mvn spring-boot:run) or thru IntelliJ. I would suggest running it first on Google Cloud SDK Shell. If it works there then I don't see any reason why it won't work on IntelliJ. When the app is ready, hit the /postMessage endpoint.

  
$ curl --data "message=Eagle has landed!" localhost:8080/postMessage
  

Now, head over to GCP PubSub messages section and we should see the messages appear. If not, try reloading the web page or do a PULL. It should look like below.

Spring Boot GCP PubSub Send Wrap Up

Did you enjoy playing around the GCP PubSub service? We now have an idea on how to send messages via the GCP PubSub messaging service. Spring has made it easy for us by abstracting whatever it is happening under the hood. Thank you for reading.

Friday, April 25, 2025

Managing HttpOnly Cookie Example

In my previous blog, Spring Boot HttpOnly Cookie Example, I demonstrated how to use the HttpOnly flag to protect your cookie. Since an HttpOnly cookie cannot be managed on the front-end, here is a demonstration on how to manage it via the back-end. For this example we'll just expire the cookie when the user clicks a button. Shouldn't be tricky so let's get right to it.

Demonstration

Before we begin, these are the tools I used to make this example:

  • IntelliJ IDEA 2023.3.4 (Community Edition)
  • openjdk-17.0.10
  • Spring Boot v3.4.1
  • Windows 11

The example code is here, github.com/jpllosa/httponly-cookie/tree/manage-httponly. Grab a copy of this branch or if you have done the Spring Boot HttpOnly Cookie Example, you can make updates to the code as you wish.

Code Changes

A couple of changes to the code. First, the endpoint to hit that then tells the browser to expire the cookie. Second, an update on the UI to trigger the cookie deletion.

LoginController.java

  
... code snipped ...
import org.springframework.web.bind.annotation.*;

@Controller
public class LoginController {

... code snipped ...

    @GetMapping("/clear-my-session")
    public @ResponseBody String clearMySession(HttpServletResponse response) {

        Cookie cookie = new Cookie("MY_SESSION", "deleted");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        return "";
    }
}

  

The /clear-my-session resource simply responds by setting the MaxAge of the cookie to zero for it to be deleted on the browser. In some other languages, in Go for example, you'll have to set the MaxAge to -1 to expire the cookie. So please read the API documentation if you're wondering why MaxAge zero doesn't work. Since we are just running on our local machhine, we don't provide the Path and Domain. When not running on your local machine, most likely you'll need to supply a Path (e.g. "/") and a Domain (e.g. example.com) to make things work.

welcome.html

  
... code snipped ...
<body>
... code snipped ...
<button id="clearMySession">JS Clear MY_SESSION</button>
<button id="beClearMySession">BE Clear MY_SESSION</button>
</body>

<script th:inline="javascript">
    $(document).ready(function() {
        $("#clearMySession").on("click", function() {
            console.log("JS removing MY_SESSION");
            document.cookie = "MY_SESSION=; expires=Thu, 01-Jan-70 00:00:01 GMT;";
        });

        $("#beClearMySession").on("click", function() {
            console.log("BE removing MY_SESSION");

            $.get("/clear-my-session", function() {
                console.log("MY_SESSION removed");
            });
        });
    });
</script>
</html>

  

Here, we added a button that hits the endpoint we created above. Simples.

Ready, Set, Go

Run the Spring Boot app. Go to the login page. Just type any username and password. You should have something like below. Open Web Tools and head over to the Storage tab (it could be called a different name on a different browser). Take note of the cookie named MY_SESSION.

As you already know from the previous blog, clicking on "JS Clear MY_SESSION" (JS for JavaScript) will not do anything. Now, try clicking on "BE Clear MY_SESSION", BE for back-end :). The MY_SESSION cookie disappears right before our eyes and you should have something like below. You can also check the network and console tabs to see what's happening behind the scenes.

Managing HttpOnly Cookie Wrap Up

There you have it. A nice way of managing your cookie from the back-end. Having the HttpOnly flag set prevents thrid parties from accessing your very important cookie. Now, it's only the back-end that can manipulate it. Thank you for reading.

Sunday, January 12, 2025

Spring Boot HttpOnly Cookie Example

Want to protect your cookie? Well, not from Santa Claus. I'm talking about HTTP Cookie. Using the HttpOnly flag when generating a cookie helps mitigate the risk of a client side script accessing the protected cookie. In other words, it won't be accessible programmatically on the client side (e.g. JavaScript). The cookie will be driven by the backend.

Why do this? To mitigate cross-site scripting (XSS) attacks. If a cross-site scripting flaw exists, and a user accidentally accesses a link that exploits this flaw, the browser will not reveal the cookie to a third party. Currently, every major browser supports HttpOnly cookies. If a browser does not support HttpOnly and a website attempts to set an HttpOnly cookie, the HttpOnly flag will be ignored by the browser, thus creating a traditional, script accessible cookie. As a result, the cookie (typically your session cookie) becomes vulnerable to theft or modification by a malicious script. Majority of XSS attacks target theft of session cookies. A server could help mitigate this issue by setting the HttpOnly flag on a cookie it creates, indicating the cookie should not be accessible on the client. If a browser that supports HttpOnly detects a cookie containing the HttpOnly flag, and client side script code attempts to read the cookie, the browser returns an empty string as the result. This causes the attack to fail by preventing the malicious (usually XSS) code from sending the data to an attacker’s website.

Demonstration

Before we begin, these are the tools I used to make this example:

  • IntelliJ IDEA 2023.3.4 (Community Edition)
  • openjdk-17.0.10
  • Spring Boot v3.4.1
  • Windows 11

The example code is here, github.com/jpllosa/httponly-cookie. Download it as you please. Now, on with the show. Run the Spring Boot app. Go to the login page, you should see something like below. Please forgive the look, this demo is about HttpOnly and not about the UI.

Without HttpOnly

Log in. Just type any user name and password. You should have something like below. Open Web Tools and head over to the Storage tab (it could be called a different name on a different browser). Take note of the HttpOnly column of the cookie named MY_SESSION.

This is the code of the Controller serving the page. Take note that we have commented out the line that sets the HttpOnly flag.

  
package com.blogspot.jpllosa.httponly_cookie;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.CookieValue;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String getLogin() {
        return "login";
    }

    @PostMapping("/login")
    public String postLogin(
            @RequestParam(name="username", required=true) String username,
            @RequestParam(name="password", required=true) String password,
            HttpServletResponse response,
            HttpSession session) {

        session.setAttribute("username", username);
        session.setAttribute("password", password);

        Cookie cookie = new Cookie("MY_SESSION", "supercalifragilisticexpialidocious");
//        cookie.setHttpOnly(true);
        response.addCookie(cookie);

        return "redirect:/welcome";
    }

    @GetMapping("/welcome")
    public String welcomeUser(
            @CookieValue(value = "MY_SESSION") String mySession,
            Model model,
            HttpSession session) {

        model.addAttribute("username", session.getAttribute("username"));
        model.addAttribute("password", session.getAttribute("password"));
        model.addAttribute("mySession", mySession);

        return "welcome";
    }

}
  

Now, click "Clear MY_SESSION" button. Notice that the cookie is gone as below.

With HttpOnly

Alright, this time, we'll put in the HttpOnly flag. Uncomment cookie.setHttpOnly(true); and restart the Spring Boot app. Log in again and as usual, have the Storage tab open. What happens when you click the "Clear MY_SESSION" button now? It's still there no matter how many times we click the button as shown on the console logs.

Spring Boot HttpOnly Cookie Conclusion

There you have it. A nice way of protecting your cookie. Having the HttpOnly flag set prevents thrid parties from accessing you very important cookie. It's now the task of the backend to manage the cookie. Thank you for reading.

Monday, January 1, 2024

XML to Java Class Example

Have you got a task to convert XML data to Java objects? Worry no more. Java 1.8 has got you covered.

For this example, we are going to use a subset of XML data from dictionary of medicines and devices. Making this as close as possible to real world use. The XML Schema Definition describes the structure of an XML document. The XML document is the data we are going to convert into Java objects. The files are:

  • f_vtm2_3301123.xml
  • vtm_v2_3.xsd

New Java Project

I'm using Spring Tool Suite 4 and Java 1.8 for this project. You can use whatever IDE you like. Create a new project like so:

XSD to Java Types

I've placed the above mentioned XML and XSD files under the data folder. I'm on Windows 10 so on the Command Prompt I go to the data folder. I have JDK configured on my PATH which makes me do the xjc command. For more information, run xjc -help. Below I have specified the -p option which specifies the target package.


D:\xml-to-java\data>xjc -p com.blogspot.jpllosa vtm_v2_3.xsd
parsing a schema...
compiling a schema...
com\blogspot\jpllosa\ObjectFactory.java
com\blogspot\jpllosa\VIRTUALTHERAPEUTICMOIETIES.java

A million thanks JDK for the xjc command. We now have Java classes created. Let's move the package to our src folder.

XML to Java

Let's create Main.java to drive the XML to Java object conversion. You should have something like below:


package com.blogspot.jpllosa;

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

public class Main {
    public static void main(String[] args) throws JAXBException {
        System.out.println("Starting...");
        File file = new File("data/f_vtm2_3301123.xml");
        JAXBContext jcVtm = JAXBContext.newInstance(VIRTUALTHERAPEUTICMOIETIES.class);
        Unmarshaller unmarshaller = jcVtm.createUnmarshaller();
        VIRTUALTHERAPEUTICMOIETIES vtms = (VIRTUALTHERAPEUTICMOIETIES) unmarshaller.unmarshal(file);
        
        int size = vtms.getVTM().size();
        if (size > 0) {
            System.out.printf("Virtual Therapeutic Moieties: %,d \r\n", size);
            for (int i = 0; i < 5; i++) {
                System.out.println(vtms.getVTM().get(i).getNM());
            }
        }
    }
}

Pretty cool. In a few minutes we are able to read the XML document and map it to Java objects. As Java objects, we can now do whatever we like with it. The super simple steps are open the file, create a JAXB context then the unmarshaller does all the heavy lifting. For this example we just show the first 5 VTMs out of the 3,000 or so Virtual Therapeutic Moieties. Right click on the file then Run As Java Application. The output looks like below:


Starting...
Virtual Therapeutic Moieties: 3,117 
Acebutolol
Paracetamol
Acetazolamide
Abciximab
Acarbose

There you have it. A super quick example of converting XML data to Java objects. The complete project can be cloned from github.com/jpllosa/xml-to-java.

Sunday, September 17, 2023

GitHub Actions Java with Maven Example

What do you hate the most about pull requests? Is it the formatting changes that clearly add no value and just makes the diff confusing? Or a PR that breaks the build? Don't you just hate a PR that breaks the build? In this example we will automate the code review by checking for a broken build when a pull request is opened. How is this done? By the power of GitHub actions.

GitHub Actions

We'll utilize my old GitHub project, github.com/jpllosa/tdd-junit from Test-Driven Development with Spring Boot Starter Test. Clicking on the Actions tab, GitHub will offer GitHub actions if you haven't created any. If you already have an Action then hit the New workflow button. For our example repo, we should see somthing like below:

GitHub Actions Java with Maven Example
GitHub Actions Java with Maven

Clicking Configure will create a template maven.yml file in .github/workflows folder as shown below. Editing a yml file in GitHub is also a nice way to search for actions. Commit then pull to have a local copy. Let's create a development branch and a feature branch (github-action-java-maven). What we would like to happen is when a pull request into development, it would automatically trigger a build, test and create a review comment if it's a broken build.

GitHub Actions Java with Maven Example
Finding GitHub Actions

$ git pull
From https://github.com/jpllosa/tdd-junit
 * [new branch]      development -> origin/development
 * [new branch]      github-action-java-maven -> origin/github-action-java-maven
Already up to date.

$ git checkout github-action-java-maven

maven.yml

Let's edit the maven.yml template. If you're using Spring Tool Suite and can't find your file, you might have to untick *. resources. Then you should be able to see the yml file.

GitHub Actions Java with Maven Example
Spring Tool Suite Filter
GitHub Actions Java with Maven Example
Spring Tool Suite Java Element Filter
GitHub Actions Java with Maven Example
GitHub Workflow YML file

Your yml file should look like below:


name: Java CI with Maven

on:
  pull_request:
    branches:
      - 'development'

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3.6.0
      - name: Set up JDK
        uses: actions/setup-java@v3.12.0
        with:
          java-version: '8'
          distribution: 'semeru'
          cache: maven
      - name: Build with Maven
        run: mvn -B clean package
      - name: View context attributes
        if: ${{ failure() }}
        uses: actions/github-script@v6.4.1
        with:
          script: |
            console.log(context);
          github-token: ${{ secrets.GITHUB_TOKEN }}
          debug: true
      - name: Create PR Comment
        if: ${{ failure() }}
        uses: actions/github-script@v6.4.1
        with:
          script: |
            github.rest.pulls.createReview({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.payload.pull_request.number,
              event: "COMMENT",
              body: "It is a mortal sin to PR a broken build! :rage:",
              comments: [],
            })
          github-token: ${{ secrets.GITHUB_TOKEN }}
          debug: true

I won't be explaining workflows in too much detail. I'll let the GitHub Docs do that for me. I will however explain this YAML file. Let's start from the top. Our workflow name is Java CI with Maven. This workflow is triggered when a pull request is opened on branch development. Our workflow run is made up of a single job, with a job ID of build. The type of machine that will process our job is the latest version of Ubuntu.

Steps, the place where all the grunt work is done. First is the Checkout. It uses the Checkout GitHub action to checkout our repository. Second it Set up JDK. It uses the Setup Java JDK GitHub action to download and set up Java version 8. Third is Build with Maven which runs the mvn -B clean package command. Fourth is the View context attributes. It uses the GitHub Script GitHub action to help us write scripts in our workflow that uses the GitHub API and the workflow run context. This step is ran when any of the previous steps has failed. The failure() is a status check function. This step is for debugging purposes only, hence the log to the console call. Fifth is the Create PR Comment action. It uses the same GitHub action as the previous one. If the Build with Maven step fails then this will create a review comment saying that it is a mortal sin to create a pull request with a broken build. LOL!

GitHub Actions in Action

Let's add a unit that fails in MathFunTest.java, push it then create a PR. Our source branch is github-action-java-maven and the target development so that the workflow gets triggered.


package com.blogspot.jpllosa;

// snipped...

@SpringBootTest
public class MathFunTest {
	// snipped...
	
	@Test
	public void testThatFails() {
		fail("Not yet implemented");
	}
}

What's the outcome? You should see something like below when creating a pull request with a broken build:

GitHub Actions Java with Maven Example
Pull Request Review Comment

Remove the failing test then push the code and you should have something like below:

GitHub Actions Java with Maven Example
GitHub Actions Success

GitHub Actions Java with Maven Summary

GitHub Actions did all the work for us. From setting up the machine with all the required Java tools. Checking out the repo and then building and running the tests. And finally creating the review comment via the GitHub Script Github action API. There you have it. An automated code review comment for those PRs breaking the build. Hope you had fun reading and applying a similar GitHub action. I know I did.