spring boot

Compiling Spring Boot into a Native Executable and Containerizing It

One of Spring Boot’s latest features is the ability to perform native compilation. This generates an application with a smaller binary size and a reduced memory footprint. This is particularly beneficial for deployments in containerized environments such as the OpenShift Container Platform.

Let’s start by installing GraalVM in our environment. I am using Fedora, so the installation process might differ for other operating systems.

$ wget https://github.com/graalvm/mandrel/releases/download/mandrel-24.2.2.0-Final/mandrel-java24-linux-amd64-24.2.2.0-Final.tar.gz
$ sudo mkdir -p /opt/mandrel
$ sudo tar -xzf mandrel-java24-linux-amd64-24.2.2.0-Final.tar.gz -C /opt/mandrel/
$ sudo ln -s /opt/mandrel/mandrel-java24-24.2.2.0-Final /opt/mandrel/current

$ export GRAALVM_HOME=/opt/mandrel/current

Next, we create a Maven pom.xml file,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>spring-boot-postgresql</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.9</version>
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.opentelemetry.instrumentation</groupId>
                <artifactId>opentelemetry-instrumentation-bom</artifactId>
                <version>2.15.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.7.7</version>
        </dependency>

        <!-- OpenTelemetry  -->
        <dependency>
            <groupId>io.opentelemetry.instrumentation</groupId>
            <artifactId>opentelemetry-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <layout>JAR</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

We also need the following Java files,

package com.edw;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
package com.edw.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Entity
@Table(name = "t_customer")

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer implements Serializable {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    @Column(name = "customer_id")
    private Long customerId;

    @Column(name = "customer_name")
    private String customerName;

}
package com.edw.repository;

import com.edw.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
package com.edw.service;

import com.edw.model.Customer;
import com.edw.repository.CustomerRepository;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Transactional
public class CustomerService {

    private final CustomerRepository customerRepository;

    public CustomerService(@Autowired CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    public List<Customer> findAll(){
        return customerRepository.findAll(Sort.by(Sort.Direction.ASC, "customerId"));
    }
}
package com.edw.controller;

import com.edw.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomerController {

    private final CustomerService customerService;

    public CustomerController(@Autowired CustomerService customerService) {
        this.customerService = customerService;
    }

    @GetMapping("/")
    public ResponseEntity findAll () {
        return ResponseEntity.ok(customerService.findAll());
    }

}

Now, build the native application using the following command,

$ mvn clean package -Pnative native:compile

......

Finished generating 'spring-boot-postgresql' in 1m 30s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:41 min
[INFO] Finished at: 2026-01-13T11:44:54+07:00
[INFO] ------------------------------------------------------------------------

If we look closely at the pom.xml provided above, we might notice something interesting. There is no profile named native explicitly defined in the file. Yet, the mvn command works perfectly.

The reason is that “spring-boot-starter-parent” comes with a pre-configured native profile out of the box. When we run Maven with the -Pnative flag, it activates this inherited profile, which automatically sets up the necessary configuration and AOT processing for GraalVM.

Create a file named Dockerfile.native-ubi9,

FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7

WORKDIR /work

RUN chown 1001 /work \
    && chmod "g+rwX" /work \
    && chown 1001:root /work

COPY target/spring-boot-postgresql /work/application
COPY target/*.so /work/

ENV LD_LIBRARY_PATH=/work

EXPOSE 8080

USER 1001

CMD ["./application"]

Then, build the container image,

$ podman build -t default-route-openshift-image-registry.apps-crc.testing/api/spring-boot-postgresql:native-ubi9 -f Dockerfile.native-ubi9 .

After deploying to OpenShift, we can see a significant resource difference between a traditional Spring Boot app and a native one


$ oc adm top pod -n api spring-boot-postgresql-jvm-64b5ff9967-5pp76
NAME                                          CPU(cores)   MEMORY(bytes)
spring-boot-postgresql-jvm-64b5ff9967-5pp76   1m           237Mi

$ oc adm top pod -n api spring-boot-postgresql-native-85db64b4cd-2cpt9
NAME                                             CPU(cores)   MEMORY(bytes)
spring-boot-postgresql-native-85db64b4cd-2cpt9   1m           81Mi

The code for this project can be found in this repository,

https://github.com/edwin/spring-boot-postgresql

Error “No marshaller registered for object of Java type” in Infinispan

Had this error whenc trying to put a Java bean to Infinispan 15,

java.lang.IllegalArgumentException: No marshaller registered for object of Java type com.edw.model.User : com.edw.model.User@1a8d9c94
	at org.infinispan.protostream.impl.SerializationContextImpl.getMarshallerDelegate(SerializationContextImpl.java:517) ~[protostream-5.0.4.Final.jar:5.0.4.Final]
	at org.infinispan.protostream.WrappedMessage.writeCustomObject(WrappedMessage.java:300) ~[protostream-5.0.4.Final.jar:5.0.4.Final]
	at org.infinispan.protostream.WrappedMessage.writeMessage(WrappedMessage.java:250) ~[protostream-5.0.4.Final.jar:5.0.4.Final]
	at org.infinispan.protostream.WrappedMessage.write(WrappedMessage.java:243) ~[protostream-5.0.4.Final.jar:5.0.4.Final]
	at org.infinispan.protostream.ProtobufUtil.toWrappedByteBuffer(ProtobufUtil.java:152) ~[protostream-5.0.4.Final.jar:5.0.4.Final]
	at org.infinispan.commons.marshall.ImmutableProtoStreamMarshaller.objectToBuffer(ImmutableProtoStreamMarshaller.java:55) ~[infinispan-commons-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.commons.marshall.AbstractMarshaller.objectToByteBuffer(AbstractMarshaller.java:70) ~[infinispan-commons-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.client.hotrod.marshall.MarshallerUtil.obj2bytes(MarshallerUtil.java:117) ~[infinispan-client-hotrod-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.client.hotrod.DataFormat$DataFormatImpl.valueToBytes(DataFormat.java:92) ~[infinispan-client-hotrod-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.client.hotrod.DataFormat.valueToBytes(DataFormat.java:211) ~[infinispan-client-hotrod-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.client.hotrod.impl.RemoteCacheImpl.valueToBytes(RemoteCacheImpl.java:628) ~[infinispan-client-hotrod-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.client.hotrod.impl.RemoteCacheImpl.putAsync(RemoteCacheImpl.java:315) ~[infinispan-client-hotrod-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.client.hotrod.impl.RemoteCacheSupport.put(RemoteCacheSupport.java:196) ~[infinispan-client-hotrod-15.0.7.Final.jar:15.0.7.Final]
	at org.infinispan.client.hotrod.impl.RemoteCacheSupport.put(RemoteCacheSupport.java:186) ~[infinispan-client-hotrod-15.0.7.Final.jar:15.0.7.Final]

Actually it happen because of my Java bean (com.edw.model.User) doesnt have any marshaller. This is how my configuration files looks like,

@Configuration
public class InfinispanConfiguration {
    @Bean
    public RemoteCacheManager remoteCacheManager() {
        return new RemoteCacheManager(
                new org.infinispan.client.hotrod.configuration.ConfigurationBuilder()
                        .addServers("localhost:11222")
                        .security().authentication().username("admin2").password("password")
                        .clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE)
                        .marshaller(ProtoStreamMarshaller.class)
                        .build());
    }
}

And everything works well after i register a marshaller for User bean

@Configuration
public class InfinispanConfiguration {
    @Bean
    public RemoteCacheManager remoteCacheManager() {
        return new RemoteCacheManager(
                new org.infinispan.client.hotrod.configuration.ConfigurationBuilder()
                        .addServers("localhost:11222")
                        .security().authentication().username("admin2").password("password")
                        .clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE)
                        .marshaller(ProtoStreamMarshaller.class)
                        .addContextInitializer(new UserIndexSchemaInitializerImpl())
                        .build());
    }
}

Where UserIndexSchemaInitializerImpl is a generated code coming from interface that extending SerializationContextInitializer class.

Full code for this can be found on my Github repository,

https://github.com/edwin/spring-boot-2-and-infinispan-15

Deploying Red Hat DataGrid 8 using Operator to Openshift 4 and Setting Up a Persistent Cache

Red Hat Data Grid, or its Open Source version which is Infinispan, is a good distributed cache and key-value NoSQL data store software developed by Red Hat which can be used as an embedded library or as a standalone server. It even can be deployed easily to a container management platform like Openshift by using Operator or Helm chart. And for this example, we are trying to deploy Red Hat Data Grid 8.4 to Openshift by using Operator.

First lets start by creating a namespace dedicated for Data Grid

$ oc new-project datagrid-ns

And create two new YAML file, one is datagrid-operator.yaml

apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
 name: datagrid
 namespace: datagrid-ns

and another one is datagrid-subscription.yaml

apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
 name: datagrid-operator
 namespace: datagrid-ns
spec:
 channel: 8.4.x
 installPlanApproval: Manual
 name: datagrid
 source: redhat-operators
 sourceNamespace: openshift-marketplace

apply them,

$ oc apply -f datagrid-operator.yaml
$ oc apply -f datagrid-subscription.yaml

it would generate a new datagrid item on “Installed Operators” page,

select “Upgrade” and “Approve” to install Data Grid Operator

a successful installation is going to looks like this,

next is creating a new Infinispan cluster, this is for sample purpose only therefore we disable TLS certificate to connect with a very minimum CPU and memory

kind: Infinispan
apiVersion: infinispan.org/v1
metadata:
  name: datagrid-cluster
  namespace: datagrid-ns
spec:
  replicas: 1
  security:
    endpointEncryption:
      type: None
  container:
    cpu: "500m:100m"
    memory: "1Gi:500Mi"

a successful configuration it would generate pods like this,

next is to expose its endpoint so it can be accessible from external

$ oc create route edge --service datagrid-cluster --hostname=datagrid.apps-crc.testing

the result is looks like this,

we can login by using credentials which is stored in Openshift’s Secret. If we are able to successfully login, we can see below Data Grid dashboard

Now lets focus on the Java part. For this, we are using Spring Boot and Java 17 which is defined in our pom.xml file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>spring-boot-with-datagrid-operator</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <version.infinispan>14.0.2.Final</version.infinispan>
        <version.protostream>4.6.2.Final</version.protostream>
        <version.spring.boot3>3.0.4</version.spring.boot3>

        <start-class>com.edw.Main</start-class>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${version.infinispan}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${version.spring.boot3}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- infinispan -->
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter-remote</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-jboss-marshalling</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-remote-query-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter-embedded</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${version.spring.boot3}</version>
                <configuration>
                    <layout>JAR</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

a Java file for configuration,

package com.edw.configuration;

import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.jboss.marshalling.commons.GenericJBossMarshaller;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InfinispanConfiguration {
    @Bean
    public RemoteCacheManager remoteCacheManager() {
        return new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("datagrid-cluster.datagrid-ns.svc.cluster.local:11222")
                        .security().authentication().username("developer").password("password")
                        .clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE)
                        .marshaller(new GenericJBossMarshaller())
                        .addJavaSerialWhiteList(".*")
                        .build());
    }
}

And this is perhaps most important class where we can define our caches. For this example, we are trying to create two different caches where one cache is persistent and another one is not.

package com.edw.helper;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.commons.configuration.XMLStringConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.UUID;

@Service
public class CacheHelper {

    private RemoteCacheManager cacheManager;

    @Autowired
    public CacheHelper (RemoteCacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public void populate() {
        // build cache without persistence
        RemoteCache cacheWithoutPersistence = cacheManager.administration().getOrCreateCache("cache-without-persistence",
                new XMLStringConfiguration("<distributed-cache name=\"cache-without-persistence\" mode=\"ASYNC\">\n" +
                        "\t<encoding media-type=\"application/x-jboss-marshalling\" />\n" +
                        "</distributed-cache>")
        );
        for (int i = 0; i < 50; i++) {
            cacheWithoutPersistence.put("key"+i, UUID.randomUUID().toString());
        }

        // build cache with persistence
        RemoteCache cacheWithPersistence = cacheManager.administration().getOrCreateCache("cache-with-persistence",
                new XMLStringConfiguration("<distributed-cache name=\"cache-with-persistence\" mode=\"ASYNC\">\n" +
                        "\t<encoding media-type=\"application/x-jboss-marshalling\" />\n" +
                        "\t<persistence passivation=\"false\">\n" +
                        "\t\t<file-store>\n" +
                        "\t\t  <index path=\"/opt/infinispan/server/data\" />\n" +
                        "\t\t  <data path=\"/opt/infinispan/server/data\" />\n" +
                        "\t\t</file-store>\n" +
                        "\t</persistence>\n" +
                        "</distributed-cache>")
        );
        for (int i = 0; i < 50; i++) {
            cacheWithPersistence.put("key"+i, UUID.randomUUID().toString());
        }
    }

    public HashMap getCacheWithoutPersistence() {
        RemoteCache<String, String> cache = cacheManager.getCache("cache-without-persistence");

        HashMap hashMap = new HashMap();
        for (Object key : cache.keySet()) {
            hashMap.put(key, cache.get(key));
        }
        return hashMap;
    }

    public HashMap getCacheWithPersistence() {
        RemoteCache<String, String> cache = cacheManager.getCache("cache-with-persistence");

        HashMap hashMap = new HashMap();
        for (Object key : cache.keySet()) {
            hashMap.put(key, cache.get(key));
        }
        return hashMap;
    }
}

with one controller file,

package com.edw.controller;

import com.edw.helper.CacheHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class IndexController {

    private CacheHelper cacheHelper;

    @Autowired
    public IndexController (CacheHelper cacheHelper) {
        this.cacheHelper = cacheHelper;
    }

    @GetMapping(path = "/cache-without-persistence")
    public HashMap cacheWithoutPersistence() {
        return cacheHelper.getCacheWithoutPersistence();
    }

    @GetMapping(path = "/cache-with-persistence")
    public HashMap cacheWithPersistence() {
        return cacheHelper.getCacheWithPersistence();
    }

    @GetMapping(path = "/populate")
    public HashMap populate() {
        cacheHelper.populate();
        return new HashMap() {{
            put("status", "success");
        }};
    }
}

After that we can build and deploy our Java application to Openshift,

Trigger populate endpoint from our Spring Boot to generate Caches and its contents,

$ curl -kv http://localhost:8080/populate
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /populate HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 14 Sep 2024 17:22:03 GMT
< 
* Connection #0 to host localhost left intact
{"status":"success"}

We can check the content of each Caches stores,

Now lets try to delete Data Grid pod and see whether all the cache data is gone or not,

$ oc delete po --grace-period=0 --force datagrid-cluster-0

We can see that cache-with-persistence Cache Store still having its data

while cache-without-persistence Cache Store is not having any data at all

Source code for this tutorial can be found here,

https://github.com/edwin/spring-boot-with-datagrid-operator

Configuring Spring Boot 3 Behind Nginx Reverse Proxy

Recently got a very unique usecase where nginx is redirecting request to a different port that is provided by Nginx. Based on below image, we can see that user is accessing port 8080 on Nginx but getting HTTP code 302 redirect to port 8081 which is not exposed by Nginx.

Workaround is quite straighforward, we can set this configuration on Spring Boot’s application properties.

server.forward-headers-strategy=FRAMEWORK

while having this configuration on nginx.conf

        location / {
                proxy_pass http://127.0.0.1:8081;
                proxy_http_version 1.1;
                proxy_set_header Connection "";
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
				proxy_set_header X-Forwarded-Port  $server_port;
                proxy_set_header X-Forwarded-Host  $host;
				
                proxy_busy_buffers_size 512k;
                proxy_buffers 8 512k;
                proxy_buffer_size 256k;
                proxy_read_timeout 1800;
                proxy_connect_timeout 1800;
                proxy_send_timeout 1800;
                client_max_body_size 50M;
                proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
                proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
                proxy_ssl_ciphers HIGH:!aNULL:!MD5;
                proxy_ssl_verify off;
                proxy_set_header cookie $http_cookie;

                port_in_redirect off;
                absolute_redirect off;
        }

We can use this Java project for testing,

https://github.com/edwin/spring-3-keycloak

Make Spring Boot Starting Time Faster

When working in a cloud environment, startup time is something that is important especially when you application is relying in HPA (Horizontal Pod Autoscale). This means that an application that can start faster is better since they can handle traffic sooner compared when having a slower startup time.

Lets take my sample Spring Boot and Camel project,

https://github.com/edwin/spring-boot-camel-json-and-wsdl

in a normal condition, this application can take almost 10second to starting-up

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.5)

2024-06-22T20:24:06.196+07:00  INFO 12328 --- [           main] com.edw.Main                             : Starting Main using Java 17.0.6 with PID 12328 (spring-boot-camel-json-and-wsdl-1.0.jar started by edwin in /tmp/spring-boot-camel-json-and-wsdl)
2024-06-22T20:24:06.198+07:00  INFO 12328 --- [           main] com.edw.Main                             : No active profile set, falling back to 1 default profile: "default"
.......
2024-06-22T20:24:14.718+07:00  INFO 12328 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 4.4.0.redhat-00019 (camel-testing) started in 1s623ms (build:0ms init:0ms start:1s623ms)
2024-06-22T20:24:14.726+07:00  INFO 12328 --- [           main] com.edw.Main                             : Started Main in 9.116 seconds (process running for 9.692)

We can see from above logs that it needs 9second to starting. But some might not see this as ideal, and start looking for some areas of improvements. And we can start by creating our Spring Boot as Lazy-Loading using below application.properties configuration

spring.main.lazy-initialization=true

Adding below Java Variables also help to increase the speed of starting up

 -XX:TieredStopAtLevel=1 -noverify

Which finally gives us this Java command

$ java -jar  -XX:TieredStopAtLevel=1 -noverify .\target\spring-boot-camel-json-and-wsdl-1.0.jar

and we can see from below logs, starting time is literally reduced into 5seconds, which is good enough.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.5)

2024-06-22T20:31:54.155+07:00  INFO 2512 --- [           main] com.edw.Main                             : Starting Main using Java 17.0.6 with PID 12328 (spring-boot-camel-json-and-wsdl-1.0.jar started by edwin in /tmp/spring-boot-camel-json-and-wsdl)
2024-06-22T20:31:54.182+07:00  INFO 2512 --- [           main] com.edw.Main                             : No active profile set, falling back to 1 default profile: "default"
........
2024-06-22T20:31:59.231+07:00  INFO 2512 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 4.4.0.redhat-00019 (camel-testing) started in 931ms (build:0ms init:0ms start:931ms)
2024-06-22T20:31:59.359+07:00  INFO 2512 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2024-06-22T20:31:59.375+07:00  INFO 2512 --- [           main] com.edw.Main                             : Started Main in 5.759 seconds (process running for 6.201)