Blog
Embracing Change: How We Migrated from Java 8 to Spring Boot 3 and Beyond

While the migration was a challenging process, it allowed us to modernise our applications and embrace the latest technologies. In this blog, we’ll take you through the steps, challenges, and lessons learned during this upgrade, with insights into how we approached the migration and resolved issues along the way.

Why Migrate? The Need for Modernisation

Performance and Security Benefits

Java 8, although solid, is now considered a legacy version. Moving to Java 21—the latest long-term support version—was crucial for ensuring we remain on a secure and high-performing platform. This allowed us to leverage improvements in the JVM, garbage collection, and language features, resulting in more efficient applications.

Compatibility with Latest Spring Boot Versions

Spring Boot 3 brings many new features and optimisations. Updating to the latest version helps ensure compatibility with modern Java libraries and frameworks, as well as offering us better support for cloud-native development, improved dependency injection, and much more.

However, migrating from older versions of Java and Spring Boot posed some challenges, and in this blog, we’ll highlight some of the significant hurdles we encountered during the process.

Key Migration Challenges and Solutions

1. Replacing javax with jakarta Libraries

One of the most impactful changes in Spring Boot 3 was the shift from javax to jakarta namespaces. This change was driven by the move from Java EE to Jakarta EE. Spring Boot 3, following this transition, no longer supports javax libraries.

The Challenge:

As our application was built on javax, we faced the daunting task of replacing all javax imports with the new jakarta equivalents. This meant updating every class, along with any third-party libraries that relied on javax namespaces.

What We Did:

We performed a comprehensive update across our codebase, replacing imports and addressing any compilation issues. We also had to fix compatibility issues with request filters and other components that relied upon the old javax namespace.

2. Migrating Input/Output Streams to Java’s Functional Interfaces

With Spring Boot 3.0, the framework embraced a more functional programming approach. Specifically, InputStream and OutputStream were replaced with more flexible interfaces like Consumer and Supplier.

The Challenge:

Spring’s older handling of streams was no longer aligned with the updated functional style, which meant we had to refactor various classes that used the old InputStream and OutputStream patterns.

What We Did:

We migrated these classes to use Consumer and Supplier interfaces, which are part of Java’s functional programming paradigm. This change not only aligned with Spring Boot 3’s functional approach but also helped reduce boilerplate code and improved the clarity of data flows.

3. Fixing Broken Unit Tests

After upgrading the libraries, we discovered that unit tests were failing—often due to incompatibilities with the new versions of Spring Boot and other libraries.

The Challenge:

Unit tests in our existing codebase started to break because many APIs had been deprecated or replaced with new implementations. Additionally, changes in third-party libraries meant that the existing mocks and related test configurations no longer worked.

What We Did:

We tackled this challenge by systematically updating each failing unit test. This involved:

  • Refactoring tests to match new APIs and interfaces.
  • Replacing outdated test libraries like Mockito and JUnit 4 with the latest versions of JUnit 5 and Mockito 5.
  • Ensuring that all tests were compatible with Spring Boot 3 and Java 21.

4. Replacing @EnableBinding with Function Interfaces

Spring Cloud Stream’s @EnableBinding annotation has been deprecated in favour of a more functional programming approach using Function interfaces. This required significant changes in how we defined event-driven components.

The Challenge:

In our older codebase, many classes relied on the @EnableBinding annotation, which needed to be replaced with the new Function interface approach. This was a substantial shift in how Spring Boot handles event-driven data flows.

What We Did:

We refactored our event-driven classes to implement the Function interfaces instead of @EnableBinding. This change aligned our codebase with the new Spring Boot standards, simplifying event handling and improving code readability.

5. Updating Dependencies and Rebuilding the Project

A migration like this often involves updating not just Java and Spring Boot versions but also the dependencies your project relies on. Libraries that were compatible with Java 8 and older Spring Boot versions may no longer be supported or work as expected with newer versions.

The Challenge:

We had to manually update several dependencies, ensuring they were compatible with Spring Boot 3 and Java 21. Some libraries required significant configuration changes, and others had to be replaced with newer alternatives.

What We Did:

We meticulously reviewed and updated each dependency, ensuring that:

  • All dependencies were compatible with Spring Boot 3 and Java 17.
  • Any deprecated or unsupported libraries were replaced with modern alternatives.
  • The project was rebuilt from scratch to ensure compatibility across the board.

6. Upgrading the Gradle Version for Compatibility

Along with upgrading Java and Spring Boot, we also had to ensure that our Gradle build system was compatible with the latest technology stack. Gradle 8.x introduced several optimisations and new features that were essential for maintaining compatibility with Java 17+ and Spring Boot 3.

The Challenge:

Our existing Gradle version was not fully compatible with Java 17+ and Spring Boot 3. Upgrading Gradle required a careful review of our build configurations and the adjustment of various plugins to avoid compatibility issues. We also had to deal with some breaking changes in Gradle’s API that affected how dependencies were resolved and how tasks were executed.

What We Did:

We upgraded our Gradle version to 8.x. This upgrade involved:

  • Updating the gradle-wrapper.properties file to use the latest Gradle distribution.
  • Adjusting Gradle tasks and plugins to align with the new version.
  • Ensuring that the build system supported Java 17’s features and that Spring Boot 3 was correctly recognised by Gradle.

This upgrade also led to better build performance, simplified dependency management, and smoother integration with other tools in our CI/CD pipeline.

7. Swagger to OpenAPI: Moving from Springfox to Springdoc

In our older setup, we used Springfox to generate Swagger API documentation. However, Springfox is no longer maintained, and it doesn’t support the latest versions of Spring Boot.

The Challenge:

The transition from Springfox to a newer API documentation generator was necessary for compatibility with Spring Boot 3 and the latest OpenAPI standards. This meant updating our API documentation setup while ensuring all existing documentation remained intact.

What We Did:

We replaced Springfox with Springdoc OpenAPI, which is fully compatible with Spring Boot 3 and supports OpenAPI 3.0. The dependency change was simple, but it required a comprehensive review of our API documentation to ensure everything was functioning properly.

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

This ensured future-proof compatibility with modern OpenAPI specifications.

8. JUnit 5 Test Configuration: Handling Test Failures

Another challenge was related to JUnit 5 test discovery and configuration. After the migration, we faced issues with tests not being detected due to incorrect or redundant dependencies.

The Challenge:

Some of our test libraries were conflicting with the new setup, causing test discovery issues. Additionally, redundant dependencies for JUnit 5 and Mockito were being included, which caused conflicts.

What We Did:

We cleaned up the test dependencies, removing unnecessary libraries and ensuring that only the required dependencies were included as shown below:

testImplementation "org.testcontainers:junit-jupiter:1.15.1"

This resolved the issues, and our test suite was back on track.

9. MongoDB and Database Migration: A Difficult Goodbye to Mongock

In our legacy system, we used MongoDB with Mongock for database migrations. Unfortunately, Mongock is not compatible with Spring Boot 3, requiring us to find an alternative.

The Challenge:

The removal of Mongock meant we had to migrate to a different database migration tool and update our existing database migration scripts.

What We Did:

We transitioned to Flyway, a widely used database migration tool, and adapted our migration scripts accordingly. While this activity is currently in progress and we are in the verge of transitioning to modern techstack, we wanted to share the insights on various challenges faced by the team along the way.

Conclusion: A Work in Progress Towards Modernisation

At Greyamp, we recently undertook a significant migration journey—upgrading our core tech stack from Java 8 to Java 21, and simultaneously transitioning from an older version of Spring Boot to the latest version. This was not just an upgrade but a necessary step to ensure better performance, security, maintainability, and compatibility with the latest tools and frameworks. While we've made significant strides in modernising our tech stack, we continue to face challenges along the way—especially with MongoDB migration and ensuring full compatibility with Spring Boot 3.

This journey has been filled with learning opportunities, as we've navigated the intricacies of updating libraries, refactoring old code, and addressing compatibility issues. While some parts of the migration are still ongoing our team remains committed to resolving these challenges and fully transitioning to the modern stack.

The work we've done so far has already resulted in improved performance, better maintainability, and a more secure platform. As we continue to address the remaining migration tasks, we are confident that each step takes us closer to a more resilient and robust platform that is in tune with the times.

Embracing Change: How We Migrated from Java 8 to Spring Boot 3 and Beyond
March 14, 2025

While the migration was a challenging process, it allowed us to modernise our applications and embrace the latest technologies. In this blog, we’ll take you through the steps, challenges, and lessons learned during this upgrade, with insights into how we approached the migration and resolved issues along the way.

Why Migrate? The Need for Modernisation

Performance and Security Benefits

Java 8, although solid, is now considered a legacy version. Moving to Java 21—the latest long-term support version—was crucial for ensuring we remain on a secure and high-performing platform. This allowed us to leverage improvements in the JVM, garbage collection, and language features, resulting in more efficient applications.

Compatibility with Latest Spring Boot Versions

Spring Boot 3 brings many new features and optimisations. Updating to the latest version helps ensure compatibility with modern Java libraries and frameworks, as well as offering us better support for cloud-native development, improved dependency injection, and much more.

However, migrating from older versions of Java and Spring Boot posed some challenges, and in this blog, we’ll highlight some of the significant hurdles we encountered during the process.

Key Migration Challenges and Solutions

1. Replacing javax with jakarta Libraries

One of the most impactful changes in Spring Boot 3 was the shift from javax to jakarta namespaces. This change was driven by the move from Java EE to Jakarta EE. Spring Boot 3, following this transition, no longer supports javax libraries.

The Challenge:

As our application was built on javax, we faced the daunting task of replacing all javax imports with the new jakarta equivalents. This meant updating every class, along with any third-party libraries that relied on javax namespaces.

What We Did:

We performed a comprehensive update across our codebase, replacing imports and addressing any compilation issues. We also had to fix compatibility issues with request filters and other components that relied upon the old javax namespace.

2. Migrating Input/Output Streams to Java’s Functional Interfaces

With Spring Boot 3.0, the framework embraced a more functional programming approach. Specifically, InputStream and OutputStream were replaced with more flexible interfaces like Consumer and Supplier.

The Challenge:

Spring’s older handling of streams was no longer aligned with the updated functional style, which meant we had to refactor various classes that used the old InputStream and OutputStream patterns.

What We Did:

We migrated these classes to use Consumer and Supplier interfaces, which are part of Java’s functional programming paradigm. This change not only aligned with Spring Boot 3’s functional approach but also helped reduce boilerplate code and improved the clarity of data flows.

3. Fixing Broken Unit Tests

After upgrading the libraries, we discovered that unit tests were failing—often due to incompatibilities with the new versions of Spring Boot and other libraries.

The Challenge:

Unit tests in our existing codebase started to break because many APIs had been deprecated or replaced with new implementations. Additionally, changes in third-party libraries meant that the existing mocks and related test configurations no longer worked.

What We Did:

We tackled this challenge by systematically updating each failing unit test. This involved:

  • Refactoring tests to match new APIs and interfaces.
  • Replacing outdated test libraries like Mockito and JUnit 4 with the latest versions of JUnit 5 and Mockito 5.
  • Ensuring that all tests were compatible with Spring Boot 3 and Java 21.

4. Replacing @EnableBinding with Function Interfaces

Spring Cloud Stream’s @EnableBinding annotation has been deprecated in favour of a more functional programming approach using Function interfaces. This required significant changes in how we defined event-driven components.

The Challenge:

In our older codebase, many classes relied on the @EnableBinding annotation, which needed to be replaced with the new Function interface approach. This was a substantial shift in how Spring Boot handles event-driven data flows.

What We Did:

We refactored our event-driven classes to implement the Function interfaces instead of @EnableBinding. This change aligned our codebase with the new Spring Boot standards, simplifying event handling and improving code readability.

5. Updating Dependencies and Rebuilding the Project

A migration like this often involves updating not just Java and Spring Boot versions but also the dependencies your project relies on. Libraries that were compatible with Java 8 and older Spring Boot versions may no longer be supported or work as expected with newer versions.

The Challenge:

We had to manually update several dependencies, ensuring they were compatible with Spring Boot 3 and Java 21. Some libraries required significant configuration changes, and others had to be replaced with newer alternatives.

What We Did:

We meticulously reviewed and updated each dependency, ensuring that:

  • All dependencies were compatible with Spring Boot 3 and Java 17.
  • Any deprecated or unsupported libraries were replaced with modern alternatives.
  • The project was rebuilt from scratch to ensure compatibility across the board.

6. Upgrading the Gradle Version for Compatibility

Along with upgrading Java and Spring Boot, we also had to ensure that our Gradle build system was compatible with the latest technology stack. Gradle 8.x introduced several optimisations and new features that were essential for maintaining compatibility with Java 17+ and Spring Boot 3.

The Challenge:

Our existing Gradle version was not fully compatible with Java 17+ and Spring Boot 3. Upgrading Gradle required a careful review of our build configurations and the adjustment of various plugins to avoid compatibility issues. We also had to deal with some breaking changes in Gradle’s API that affected how dependencies were resolved and how tasks were executed.

What We Did:

We upgraded our Gradle version to 8.x. This upgrade involved:

  • Updating the gradle-wrapper.properties file to use the latest Gradle distribution.
  • Adjusting Gradle tasks and plugins to align with the new version.
  • Ensuring that the build system supported Java 17’s features and that Spring Boot 3 was correctly recognised by Gradle.

This upgrade also led to better build performance, simplified dependency management, and smoother integration with other tools in our CI/CD pipeline.

7. Swagger to OpenAPI: Moving from Springfox to Springdoc

In our older setup, we used Springfox to generate Swagger API documentation. However, Springfox is no longer maintained, and it doesn’t support the latest versions of Spring Boot.

The Challenge:

The transition from Springfox to a newer API documentation generator was necessary for compatibility with Spring Boot 3 and the latest OpenAPI standards. This meant updating our API documentation setup while ensuring all existing documentation remained intact.

What We Did:

We replaced Springfox with Springdoc OpenAPI, which is fully compatible with Spring Boot 3 and supports OpenAPI 3.0. The dependency change was simple, but it required a comprehensive review of our API documentation to ensure everything was functioning properly.

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

This ensured future-proof compatibility with modern OpenAPI specifications.

8. JUnit 5 Test Configuration: Handling Test Failures

Another challenge was related to JUnit 5 test discovery and configuration. After the migration, we faced issues with tests not being detected due to incorrect or redundant dependencies.

The Challenge:

Some of our test libraries were conflicting with the new setup, causing test discovery issues. Additionally, redundant dependencies for JUnit 5 and Mockito were being included, which caused conflicts.

What We Did:

We cleaned up the test dependencies, removing unnecessary libraries and ensuring that only the required dependencies were included as shown below:

testImplementation "org.testcontainers:junit-jupiter:1.15.1"

This resolved the issues, and our test suite was back on track.

9. MongoDB and Database Migration: A Difficult Goodbye to Mongock

In our legacy system, we used MongoDB with Mongock for database migrations. Unfortunately, Mongock is not compatible with Spring Boot 3, requiring us to find an alternative.

The Challenge:

The removal of Mongock meant we had to migrate to a different database migration tool and update our existing database migration scripts.

What We Did:

We transitioned to Flyway, a widely used database migration tool, and adapted our migration scripts accordingly. While this activity is currently in progress and we are in the verge of transitioning to modern techstack, we wanted to share the insights on various challenges faced by the team along the way.

Conclusion: A Work in Progress Towards Modernisation

At Greyamp, we recently undertook a significant migration journey—upgrading our core tech stack from Java 8 to Java 21, and simultaneously transitioning from an older version of Spring Boot to the latest version. This was not just an upgrade but a necessary step to ensure better performance, security, maintainability, and compatibility with the latest tools and frameworks. While we've made significant strides in modernising our tech stack, we continue to face challenges along the way—especially with MongoDB migration and ensuring full compatibility with Spring Boot 3.

This journey has been filled with learning opportunities, as we've navigated the intricacies of updating libraries, refactoring old code, and addressing compatibility issues. While some parts of the migration are still ongoing our team remains committed to resolving these challenges and fully transitioning to the modern stack.

The work we've done so far has already resulted in improved performance, better maintainability, and a more secure platform. As we continue to address the remaining migration tasks, we are confident that each step takes us closer to a more resilient and robust platform that is in tune with the times.

Subscribe To Our Newsletter

Do get in touch with us to understand more about how we can help your organization in building meaningful and in-demand products
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Blog

Embracing Change: How We Migrated from Java 8 to Spring Boot 3 and Beyond

Written by:  

Aaquib

March 14, 2025

8 min read

Embracing Change: How We Migrated from Java 8 to Spring Boot 3 and Beyond

While the migration was a challenging process, it allowed us to modernise our applications and embrace the latest technologies. In this blog, we’ll take you through the steps, challenges, and lessons learned during this upgrade, with insights into how we approached the migration and resolved issues along the way.

Why Migrate? The Need for Modernisation

Performance and Security Benefits

Java 8, although solid, is now considered a legacy version. Moving to Java 21—the latest long-term support version—was crucial for ensuring we remain on a secure and high-performing platform. This allowed us to leverage improvements in the JVM, garbage collection, and language features, resulting in more efficient applications.

Compatibility with Latest Spring Boot Versions

Spring Boot 3 brings many new features and optimisations. Updating to the latest version helps ensure compatibility with modern Java libraries and frameworks, as well as offering us better support for cloud-native development, improved dependency injection, and much more.

However, migrating from older versions of Java and Spring Boot posed some challenges, and in this blog, we’ll highlight some of the significant hurdles we encountered during the process.

Key Migration Challenges and Solutions

1. Replacing javax with jakarta Libraries

One of the most impactful changes in Spring Boot 3 was the shift from javax to jakarta namespaces. This change was driven by the move from Java EE to Jakarta EE. Spring Boot 3, following this transition, no longer supports javax libraries.

The Challenge:

As our application was built on javax, we faced the daunting task of replacing all javax imports with the new jakarta equivalents. This meant updating every class, along with any third-party libraries that relied on javax namespaces.

What We Did:

We performed a comprehensive update across our codebase, replacing imports and addressing any compilation issues. We also had to fix compatibility issues with request filters and other components that relied upon the old javax namespace.

2. Migrating Input/Output Streams to Java’s Functional Interfaces

With Spring Boot 3.0, the framework embraced a more functional programming approach. Specifically, InputStream and OutputStream were replaced with more flexible interfaces like Consumer and Supplier.

The Challenge:

Spring’s older handling of streams was no longer aligned with the updated functional style, which meant we had to refactor various classes that used the old InputStream and OutputStream patterns.

What We Did:

We migrated these classes to use Consumer and Supplier interfaces, which are part of Java’s functional programming paradigm. This change not only aligned with Spring Boot 3’s functional approach but also helped reduce boilerplate code and improved the clarity of data flows.

3. Fixing Broken Unit Tests

After upgrading the libraries, we discovered that unit tests were failing—often due to incompatibilities with the new versions of Spring Boot and other libraries.

The Challenge:

Unit tests in our existing codebase started to break because many APIs had been deprecated or replaced with new implementations. Additionally, changes in third-party libraries meant that the existing mocks and related test configurations no longer worked.

What We Did:

We tackled this challenge by systematically updating each failing unit test. This involved:

  • Refactoring tests to match new APIs and interfaces.
  • Replacing outdated test libraries like Mockito and JUnit 4 with the latest versions of JUnit 5 and Mockito 5.
  • Ensuring that all tests were compatible with Spring Boot 3 and Java 21.

4. Replacing @EnableBinding with Function Interfaces

Spring Cloud Stream’s @EnableBinding annotation has been deprecated in favour of a more functional programming approach using Function interfaces. This required significant changes in how we defined event-driven components.

The Challenge:

In our older codebase, many classes relied on the @EnableBinding annotation, which needed to be replaced with the new Function interface approach. This was a substantial shift in how Spring Boot handles event-driven data flows.

What We Did:

We refactored our event-driven classes to implement the Function interfaces instead of @EnableBinding. This change aligned our codebase with the new Spring Boot standards, simplifying event handling and improving code readability.

5. Updating Dependencies and Rebuilding the Project

A migration like this often involves updating not just Java and Spring Boot versions but also the dependencies your project relies on. Libraries that were compatible with Java 8 and older Spring Boot versions may no longer be supported or work as expected with newer versions.

The Challenge:

We had to manually update several dependencies, ensuring they were compatible with Spring Boot 3 and Java 21. Some libraries required significant configuration changes, and others had to be replaced with newer alternatives.

What We Did:

We meticulously reviewed and updated each dependency, ensuring that:

  • All dependencies were compatible with Spring Boot 3 and Java 17.
  • Any deprecated or unsupported libraries were replaced with modern alternatives.
  • The project was rebuilt from scratch to ensure compatibility across the board.

6. Upgrading the Gradle Version for Compatibility

Along with upgrading Java and Spring Boot, we also had to ensure that our Gradle build system was compatible with the latest technology stack. Gradle 8.x introduced several optimisations and new features that were essential for maintaining compatibility with Java 17+ and Spring Boot 3.

The Challenge:

Our existing Gradle version was not fully compatible with Java 17+ and Spring Boot 3. Upgrading Gradle required a careful review of our build configurations and the adjustment of various plugins to avoid compatibility issues. We also had to deal with some breaking changes in Gradle’s API that affected how dependencies were resolved and how tasks were executed.

What We Did:

We upgraded our Gradle version to 8.x. This upgrade involved:

  • Updating the gradle-wrapper.properties file to use the latest Gradle distribution.
  • Adjusting Gradle tasks and plugins to align with the new version.
  • Ensuring that the build system supported Java 17’s features and that Spring Boot 3 was correctly recognised by Gradle.

This upgrade also led to better build performance, simplified dependency management, and smoother integration with other tools in our CI/CD pipeline.

7. Swagger to OpenAPI: Moving from Springfox to Springdoc

In our older setup, we used Springfox to generate Swagger API documentation. However, Springfox is no longer maintained, and it doesn’t support the latest versions of Spring Boot.

The Challenge:

The transition from Springfox to a newer API documentation generator was necessary for compatibility with Spring Boot 3 and the latest OpenAPI standards. This meant updating our API documentation setup while ensuring all existing documentation remained intact.

What We Did:

We replaced Springfox with Springdoc OpenAPI, which is fully compatible with Spring Boot 3 and supports OpenAPI 3.0. The dependency change was simple, but it required a comprehensive review of our API documentation to ensure everything was functioning properly.

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

This ensured future-proof compatibility with modern OpenAPI specifications.

8. JUnit 5 Test Configuration: Handling Test Failures

Another challenge was related to JUnit 5 test discovery and configuration. After the migration, we faced issues with tests not being detected due to incorrect or redundant dependencies.

The Challenge:

Some of our test libraries were conflicting with the new setup, causing test discovery issues. Additionally, redundant dependencies for JUnit 5 and Mockito were being included, which caused conflicts.

What We Did:

We cleaned up the test dependencies, removing unnecessary libraries and ensuring that only the required dependencies were included as shown below:

testImplementation "org.testcontainers:junit-jupiter:1.15.1"

This resolved the issues, and our test suite was back on track.

9. MongoDB and Database Migration: A Difficult Goodbye to Mongock

In our legacy system, we used MongoDB with Mongock for database migrations. Unfortunately, Mongock is not compatible with Spring Boot 3, requiring us to find an alternative.

The Challenge:

The removal of Mongock meant we had to migrate to a different database migration tool and update our existing database migration scripts.

What We Did:

We transitioned to Flyway, a widely used database migration tool, and adapted our migration scripts accordingly. While this activity is currently in progress and we are in the verge of transitioning to modern techstack, we wanted to share the insights on various challenges faced by the team along the way.

Conclusion: A Work in Progress Towards Modernisation

At Greyamp, we recently undertook a significant migration journey—upgrading our core tech stack from Java 8 to Java 21, and simultaneously transitioning from an older version of Spring Boot to the latest version. This was not just an upgrade but a necessary step to ensure better performance, security, maintainability, and compatibility with the latest tools and frameworks. While we've made significant strides in modernising our tech stack, we continue to face challenges along the way—especially with MongoDB migration and ensuring full compatibility with Spring Boot 3.

This journey has been filled with learning opportunities, as we've navigated the intricacies of updating libraries, refactoring old code, and addressing compatibility issues. While some parts of the migration are still ongoing our team remains committed to resolving these challenges and fully transitioning to the modern stack.

The work we've done so far has already resulted in improved performance, better maintainability, and a more secure platform. As we continue to address the remaining migration tasks, we are confident that each step takes us closer to a more resilient and robust platform that is in tune with the times.

About Greyamp

Greyamp is a boutique Management Consulting firm that works with large enterprises to help them on their Digital Transformation journeys, going across the organisation, covering process, people, culture, and technology. Subscribe here to get our latest digital transformation insights.