Java 23, SpringBoot 3.3.4, Jakarta 10
A rapid-start template designed for application development, optimized specifically for microservices-based and cloud-native architecture approaches.

Key Features of the Java 23, SpringBoot 334 Template
- The code’s packaging structure adheres to a Hexagonal Architecture, (proposed by Dr. Alistair Cockburn in 2005) utilizing ports and adapters to maintain a clear separation of concerns.
- Cross-cutting concerns, such as exception handling, logging, and security, are implemented using Aspect-Oriented Programming (AOP).
- Authentication and authorization are managed using JSON Web Tokens (JWT), which incorporate both authentication and data tokens.
- Robust Web Security measures effectively manage incoming requests and outgoing responses.
- Comprehensive log management, including file rollover mechanisms, standardised log formats, and the logging of critical information.
- Supports multiple Spring profiles: Development, Staging, and Production. Integrates H2 Database for the development environment, with PostgreSQL used in both staging and production.
- Docker containerisation: Builds Docker containers along with scripts for comprehensive container testing.
Hexagonal Architecture

Hexagonal Architecture, also known as Ports and Adapters, is a software design pattern that promotes a clear separation between the core logic of an application and the different external systems it interacts with. Introduced by Alistair Cockburn, it aims to create flexible and maintainable applications that can adapt to changes over time without significant rewrites. This architectural style is especially advantageous in microservices-based systems, where services need to remain modular, easy to scale, and loosely coupled.
Hexagonal Architecture Overview
Hexagonal Architecture consists of the following key concepts:
1. Domain/Core Logic:
- At the heart of Hexagonal Architecture is the domain model or core logic — the “application’s business logic.” This part is purely focused on implementing the core functionality of the application without any dependencies on external systems like databases or APIs. It defines how the application behaves and processes data.
2. Ports:
- Ports are interfaces that define the input and output mechanisms for interacting with the core application logic. They define the way in which the core should be accessed or extended without being tied to any specific technology or framework. Ports represent the contract of how interactions should occur.
- Ports come in two types: driving ports (inputs to the core, such as requests from the user or external systems) and driven ports (outputs from the core, such as calls to a repository or another service).
3. Adapters:
- Adapters are concrete implementations that fulfill the contracts specified by ports. They “adapt” external components like databases, message queues, or user interfaces to communicate with the core logic. Adapters are the specific mechanisms that bridge the external systems with the core.
- Examples include REST API controllers, repositories, Kafka consumers, and so forth.
4. Ports and Adapters Interaction:
- The ports provide a set of operations or interactions, and the adapters implement those operations. This decoupling means that changes in external components (like migrating from one database to another) do not require changes to the core domain logic, thus providing a level of independence between the different parts of the application.
In a microservices architecture, individual services are intended to be independently deployable, loosely coupled, and focused on a single domain. Hexagonal Architecture provides several benefits when applied to microservices, making it particularly well-suited for this type of architecture:
Benefits of Hexagonal Architecture
- Testability: Simplifies unit testing by isolating the core logic.
- Adaptability: Enables swapping or modifying infrastructure with minimal impact.
- Separation of Concerns: Ensures domain logic is independent of technical details.
- Scalability: Facilitates modular development and scalability of individual microservices.
- Parallel Development: Encourages faster development by decoupling components.
- Technology Independence: Allows flexible adoption of different tools and technologies.
Hexagonal Architecture provides a robust foundation for building microservices by emphasizing clear boundaries, modularity, and independence of external systems. These benefits align well with the goals of microservices, allowing each service to be independently developed, tested, and evolved while keeping the core business logic clean and focused. As microservices grow more complex, adopting Hexagonal Architecture can make them more maintainable, resilient, and adaptable in the face of changing requirements and technologies.
Package Hierarchy based on Hexagonal Architecture

1. Adapters Package
The adapters package is responsible for holding the core implementations that interact directly with external and internal system boundaries. This includes all the REST endpoints that expose the microservice’s APIs, allowing external clients or other services to communicate with it. The package also contains repositories, which manage data access logic, providing seamless integration between the application and the underlying data store.
Additionally, the adapter’s package hosts business service implementations — these services contain the essential business logic that processes data and implements the core functionality of the microservice. Beyond these components, the adapter’s package also includes various filters that handle request and response modifications as per the service’s requirements. It leverages Aspect-Oriented Programming (AOP) to manage cross-cutting concerns like exception handling, logging, and security, promoting cleaner and more maintainable code.
2. Domain (Port) Package
The Domain (port) package acts as an abstraction layer and defines interfaces, entities, and records that represent the essential data model. The interfaces within the port package describe the contract for the services and repositories, which are then implemented by the adapters package. This separation ensures that the core business logic remains decoupled from specific implementation details, thereby adhering to the principles of Hexagonal Architecture.
The package also contains data model entities, which define the structure of the data handled by the microservice, and records that represent immutable data structures. In addition, all essential exception definitions are found here, which are later used within the adapters implementations. By placing these exceptions in the port package, you ensure that all implementations of the defined interfaces handle errors in a consistent manner, improving robustness and standardization across the service.
3. Server Package
The server package encapsulates all server-related functionality that ensures the microservice is configured properly and is capable of performing its responsibilities effectively. It includes configurations for databases, which define how the service connects to and manages interactions with its underlying data store. It also provides cache configurations to improve performance by reducing the need to fetch data repeatedly from the database for frequently requested information.
The server package may also include messaging configurations, such as those for Kafka, which allows the service to produce and consume events as part of a larger distributed architecture. Moreover, it contains configuration objects mapped from application properties to centralize settings for easy customization. The package has code for handling token re-generation, such as refreshing expired tokens; however, it’s worth noting that such logic should ideally be managed by a dedicated authentication service rather than the core server to promote separation of concerns and enhance security.
4. Security Package
The security package contains all the logic needed to secure the microservice. It includes implementations for JSON Web Tokens (JWT), which are used to authenticate and authorize requests by verifying credentials and controlling access. The package also provides components for creating and verifying digital signatures, as well as handling other cryptographic operations to ensure data integrity and secure communication within and outside the microservice.
While this package is comprehensive, not all microservices will require the entire range of security functionalities offered. For instance, some services may only need simple token validation, whereas others might require more complex cryptographic operations. As a result, the package is designed to be modular, allowing for selective use of its features as per the specific security requirements of each service.
5. Utils Package
The utils package is home to general utility functions that are commonly required throughout the application or in multiple services. These utilities might include string manipulation helpers, date and time conversion functions, or validation methods that are used frequently in different parts of the codebase. By centralizing these utilities, you avoid redundancy, reduce the chances of inconsistencies, and make the code easier to maintain. The utils package plays a vital role in ensuring that shared logic is reusable and that any updates can be applied universally with minimal effort, making the overall development process more efficient and cohesive.

Service Bootstrap — Spring Application
public class ServiceBootStrap {
// ... Only Relevant Code is shown for Swagger Docs - Open API v3
/**
* Start the Microservice
* API URL : http://localhost:9334/ms-vanilla/api/v1/swagger-ui/index.html
* @param args
*/
public static void main(String[] args) {
start(args); // Start the Server
}
/**
* Start the Server
* @param args
*/
public static void start(String[] args) {
log.info("Booting MicroService ..... ..");
try {
context = SpringApplication.run(ServiceBootStrap.class, args);
// Set a default profile if no other profile is specified
ConfigurableEnvironment environment = context.getEnvironment();
if (environment.getActiveProfiles().length == 0) {
log.info("Profile is missing, so defaulting to "+ activeProfile +" Profile!");
environment.addActiveProfile(activeProfile);
}
log.info("Booting MicroService ..... ... Startup completed!");
} catch (Exception e) {
e.printStackTrace();
}
}
// ... Only Relevant code is shown...
}

When the service starts, it displays key information about its configuration:
- Version Information: Includes the service version, Java runtime version, and Spring Boot version.
- Build Details: Displays the build number and the build date.
- Service Mode: Indicates the current deployment mode, whether Development, Staging, or Production.
Swagger UI — Open API v3

Open API — Setup in the Service Bootstrap Code
The following code defines several @Bean methods that use the Springdoc OpenAPI library to generate Swagger documentation for different API groups within the microservice. The goal is to segment API documentation into logical parts, such as the core service, system health, and configuration endpoints. This segmentation allows developers and users to view specific parts of the API documentation in a focused manner.
1. allPublicApi():
- Defines a grouped API named as serviceName-service (e.g., myapp-service).
- This group includes all the public API paths for the service, matched using a pattern (pathsToMatch) to include every endpoint under serviceConfig.getServiceApiPath().
- It’s useful for providing a comprehensive overview of all available endpoints.
2. appPublicApi():
- Defines a grouped API named as serviceName-service-core — which is App Specific API calls.
- This group includes all core service API paths but explicitly excludes certain paths, such as /service/** and /config/**. These exclusions are specified using pathsToExclude.
- This helps in isolating the core functionality of the service from configuration and health-related endpoints.
3. configPublicApi():
- Defines a grouped API named as serviceName-service-config.
- This group focuses on configuration-related endpoints by matching paths like /config/**.
- The comment indicates that this documentation group references the HealthController, implying that it includes endpoints related to service configuration and health checks.
4. systemPublicApi():
- Defines a grouped API named as serviceName-service-health.
- This group matches paths related to system health and service-level checks, typically with paths like /service/**.
- It serves to separate health monitoring endpoints from core API functionality.
5. buildOpenAPI():
- Defines the overall OpenAPI specification for the service.
- Sets metadata such as the title, description, version, license, and links to external documentation (e.g., source code repository).
- It includes a SecurityScheme component to specify the Bearer Token Authentication (JWT) scheme. This is crucial for services that require authentication and authorization, allowing the OpenAPI documentation to display secured endpoints correctly.
- The servers field is used to provide server configuration details (using the getServers() method, which is to provide a list of server URLs).
public class ServiceBootStrap {
// ... Only Relevant Code is shown for Swagger Docs - Open API v3
/**
* Open API v3 Docs - All
* Reference: https://springdoc.org/faq.html
* @return
*/
@Bean
public GroupedOpenApi allPublicApi() {
return GroupedOpenApi.builder()
.group(serviceConfig.getServiceName()+"-service")
.pathsToMatch(serviceConfig.getServiceApiPath()+"/**")
.build();
}
/**
* Open API v3 Docs - MicroService
* Reference: https://springdoc.org/faq.html
* @return
*/
@Bean
public GroupedOpenApi appPublicApi() {
return GroupedOpenApi.builder()
.group(serviceConfig.getServiceName()+"-service-core")
.pathsToMatch(serviceConfig.getServiceApiPath()+"/**")
.pathsToExclude(serviceConfig.getServiceApiPath()+"/service/**", serviceConfig.getServiceApiPath()+"/config/**")
.build();
}
/**
* Open API v3 Docs - Core Service
* Reference: https://springdoc.org/faq.html
* Change the Resource Mapping in HealthController
*
* @see HealthController
*/
@Bean
public GroupedOpenApi configPublicApi() {
// System.out.println;
return GroupedOpenApi.builder()
.group(serviceConfig.getServiceName()+"-service-config")
.pathsToMatch(serviceConfig.getServiceApiPath()+"/config/**")
.build();
}
@Bean
public GroupedOpenApi systemPublicApi() {
return GroupedOpenApi.builder()
.group(serviceConfig.getServiceName()+"-service-health")
.pathsToMatch(serviceConfig.getServiceApiPath()+"/service/**")
.build();
}
/**
* Open API v3
* Reference: https://springdoc.org/faq.html
* @return
*/
@Bean
public OpenAPI buildOpenAPI() {
return new OpenAPI()
.servers(getServers())
.info(new Info()
.title(serviceConfig.getServiceName()+" Service")
.description(serviceConfig.getServiceDetails())
.version(serviceConfig.getServerVersion())
.license(new License().name("License: "+serviceConfig.getServiceLicense())
.url(serviceConfig.getServiceUrl()))
)
.externalDocs(new ExternalDocumentation()
.description(serviceConfig.getServiceName()+" Service Source Code")
.url(serviceConfig.getServiceApiRepository())
)
.components(new Components().addSecuritySchemes("bearer-key",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT"))
);
}
// ... Only relevant code is shown...
}
This code effectively leverages Springdoc OpenAPI to provide a complete, user-friendly Swagger UI for API documentation, enabling easy interaction with the endpoints, better understanding of the microservice, and secure testing of endpoints.
Open API — REST Endpoint Documentation
This Spring Boot Java code defines a REST controller that provides functionality for managing products in an API. The controller uses OpenAPI (Swagger) annotations to generate API documentation that is useful for understanding, testing, and integrating the service. Let’s break down the relevant annotations and elements from the OpenAPI Swagger documentation perspective:
1. Class-Level Annotations
@RestController:
- This annotation indicates that this class is a REST controller, meaning it will handle HTTP requests and provide responses in a RESTful manner.
@RequestMapping(“${service.api.path}/product”):
- This annotation maps all requests to the controller to a base URL path, which is dynamically set via the property service.api.path (e.g., /ms-vanilla/api/v1). Combined with /product, this results in a base path like /ms-vanilla/api/v1/product.
- The use of dynamic properties makes it easier to change the base path from a configuration file without modifying the code.
@RequestScope:
- This annotation indicates that a new instance of the ProductControllerImpl will be created for each request. This helps in ensuring that each request is isolated, but it’s not directly related to Swagger documentation.
@Tag(name = “Product API”, description = “Search Products, Create Products, Activate / DeActivate, Delete & Update Product”):
- The @Tag annotation is part of OpenAPI and is used to group API endpoints under a common name and description in the Swagger UI.
- In this case, all endpoints in this controller will be grouped under the “Product API”, with a description of what functionalities are available (search, create, activate/deactivate, delete, and update products).
2. Method-Level Annotations
The getProductStatus method is responsible for fetching the status of a product using its UUID. This method is documented using OpenAPI annotations to generate the relevant information for Swagger documentation:
@Operation(summary = “Get the Product By Product UUID”):
- This annotation is used to provide a summary for the endpoint in the Swagger documentation.
- It describes what the endpoint does, making it easier for consumers to understand. Here, it provides information that this endpoint retrieves a product by its UUID.
@ApiResponses:
- This annotation describes the possible responses that the endpoint can return, helping users understand the expected outcomes and potential error conditions.
- The @ApiResponses annotation includes multiple @ApiResponse annotations to specify different possible HTTP responses.
@ApiResponse
- (responseCode = “200”, description = “Product Retrieved for status check”, content = {@Content(mediaType = “application/json”)}):
- Describes the successful response with HTTP status code 200.
- The response content type is specified as application/json, meaning the response will be a JSON representation of the product data.
- (responseCode = “400”, description = “Invalid Product ID.”, content = @Content):
- Describes a 400 Bad Request response when an invalid product ID is provided.
- This helps document the error scenarios clearly, so API consumers know what errors to expect and how to handle them.
@GetMapping(“/status/{productId}”):
- Maps HTTP GET requests to the /status/{productId} path. This means the endpoint can be accessed by a URL like /ms-vanilla/api/v1/product/status/{productId}.
- This part of the URL is dynamic ({productId}), meaning a product ID must be provided in the request URL.
@ResponseBody:
- Indicates that the return value of the method will be written directly to the response body in JSON format (enabled by Spring Boot’s @RestController behavior).
@RestController
// "/ms-vanilla/api/v1"
@RequestMapping("${service.api.path}/product")
@RequestScope
@Tag(name = "Product API", description = "Search Products, Create Products, Activate / DeActivate, Delete & Update Product")
public class ProductControllerImpl extends AbstractController {
// ... Only Relevant Code is shown...
/**
* GET Method Call to Check the Product Status
*
* @return
*/
@Operation(summary = "Get the Product By Product UUID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Product Retrieved for status check",
content = {@Content(mediaType = "application/json")}),
@ApiResponse(responseCode = "400",
description = "Invalid Product ID.",
content = @Content)
})
@GetMapping("/status/{productId}")
@ResponseBody
public ResponseEntity<StandardResponse> getProductStatus(@PathVariable("productId") UUID _productId,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
log.debug("|"+name()+"|Request to Get Product Status.. "+_productId);
ProductEntity product = productServiceImpl.getProductById(_productId);
StandardResponse stdResponse = createSuccessResponse("Data Fetch Success!");
stdResponse.setPayload(product);
return ResponseEntity.ok(stdResponse);
}
// ... Only Relevant Code is shown...
}
- The ProductControllerImpl class defines RESTful endpoints for managing products, with a focus on OpenAPI documentation using Swagger annotations.
- The class-level annotation @Tag groups all product-related endpoints, while method-level annotations @Operation and @ApiResponses provide detailed information on what each endpoint does and the possible responses.
Swagger — Open API Dependencies
<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-common -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-common</artifactId>
<version>1.8.0</version>
</dependency>
The Swagger documentation generated from these annotations makes it easy for developers and users to understand, interact with, and integrate the API into their applications. It helps improve transparency, usability, and ease of testing for the Product API.
In the next section (Part 2), we will explore additional code examples related to Exception Handling, Logging, Security (specifically using JSON Web Tokens), and Filters. These implementations are designed to serve as standard components applicable across any microservice.
GitHub Repository: ms-springboot-334-vanilla Microservice with SpringBoot 3.3.4, Java 23 and Jakarta EE 10. Microservices with AOP, Exception Handling, Logging, Crypto and Security (JWT) Framework.
Enjoy the week with a Cup of Java…
Java 23, SpringBoot 3.3.4 & Jakarta 10 Series
- Java 23, SpringBoot 3.3.4 & Jakarta 10 — Part 1 (This article)
- Java 23, SpringBoot 3.3.4: AOP Exception Handling — Part 2
- Java 23, SpringBoot 3.3.4: Logback Setup — Part 3
- Java 23, SpringBoot 3.3.4: Log/Events: API Flow & Logging — Part 4
- Java 23, SpringBoot 3.3.4: Metrics: Micrometer, Prometheus, Actuator — Part 5
- Java 23, SpringBoot 3.3.4: Metrics: Micrometer & AOP — Part 6
- Java 23, SpringBoot 3.3.4: Tracing: OpenTelemetry — Part 7
- Java 23, SpringBoot 3.3.4: Tracing: OpenTelemetry In Action — Part 8 Coming Soon
- Java 23, SpringBoot 3.3.4: Filters: Security, Log — Part 9 Coming Soon
- Java 23, SpringBoot 3.3.4: AOP: Spring Security — Part 10 Coming Soon
- Java 23, SpringBoot 3.3.4: CRUD — Part 11 Coming Soon
- Java 23, SpringBoot 3.3.4: CRUD Queries & Page Sort — Part 12 Coming Soon
Further Research
- Hexagonal Architecture — NetFlix Tech Blog, 2020
- Hexagonal Architecture — Wikipedia
- Amazon — Hexagonal Architecture
- Spring: Aspect Oriented Programming (AOP) with Spring
- Naveen Metta — Deep Dive into Aspect-Oriented Programming in Spring
- Geek for Geeks — Aspect-Oriented Programming and Spring
- JWT — JSON Web Tokens
- Descope — What is a JWT and How it Works?
- Security Flag — Broken JSON Web Token
- Swagger IO — Open API Documentation
