This repository contains Spring Boot autoconfiguration starters that validate incoming HTTP request headers for both Spring MVC (Servlet) and Spring WebFlux applications, plus demo applications showing how to use each variant.
The starters integrate at the framework level and expose configuration properties to declare which headers are required and how they should be validated. They also ship lightweight request/response filters and an exception handler that returns a standardised error payload when validation fails.
Modules in this repo are separate Maven projects:
header-validator-common: shared, web‑stack agnostic logicheader-validator-starter-web: Servlet/MVC auto‑configuration starter (updated artefact for the web/servlet variant)header-validator-starter-webflux: WebFlux auto‑configuration starterdemo-web: Spring MVC demo app consuming the Servlet starterdemo-webflux: Spring WebFlux demo app consuming the WebFlux starter
- Language: Java 21
- Frameworks: Spring Boot 3.5.9 (Servlet and WebFlux)
- Build tool: Maven, with Maven Wrapper (
mvnw,mvnw.cmd) in root - Testing: JUnit 5, Spring Boot Test, Rest Assured (demo)
- Default header rules are provided and can be extended/overridden with configuration properties.
- Pluggable validation via
HeaderValidator(functional interface). Built-ins include:- Regex-based validator for values like
X-MinorServiceVersionandX-CallBackURL - Epoch timestamp validator for
X-TimeStamp
- Regex-based validator for values like
- Error handling: Invalid or missing headers are collected; a 400 Bad Request is returned with a standardised body
(fields such as
messageCode=4000453,statusDescription=Failed, and one error per offending header). - Filters:
RequestContextFiltercreates a request-scoped context with a conversation ID.
- Java 21 (JDK 21)
- Maven 3.9+ (or use the included Maven Wrapper)
-
Build and install all modules locally (from the repository root):
Unix/macOS:
./mvnw clean install
Windows PowerShell:
./mvnw.cmd clean install
This installs the starters (
header-validator-starter-webandheader-validator-starter-webflux) to your local Maven repository so the demos can consume them. -
Add the dependency (choose ONE per application):
Servlet/MVC application (Tomcat/Jetty/Undertow):
<dependency> <groupId>ke.co.xently</groupId> <artifactId>header-validator-starter-web</artifactId> <version>3.1.0</version> </dependency>
WebFlux application (Reactor Netty):
<dependency> <groupId>ke.co.xently</groupId> <artifactId>header-validator-starter-webflux</artifactId> <version>3.1.0</version> </dependency>
Important: Do not add both starters to the same application. Use the one that matches your web runtime.
-
Run the demo application(s):
Unix/macOS:
cd demo-web # OR demo-webflux ./mvnw spring-boot:run
Windows PowerShell:
Set-Location demo-web # OR demo-webflux ./mvnw.cmd spring-boot:runThen call the demo endpoint (requires headers; see below):
GET http://localhost:8080/api/hello
Sample call file: request.http.
Both starters expose the following configuration properties (Spring Boot relaxed binding applies):
-
xently.api.headers.validation.headers— a list of header rules. Each rule supports:header-name(string, required): The HTTP header name.required(boolean, default true): Whether the header must be present and non-empty.validator(string, optional): AHeaderValidatorimplementation. The value can be:- a fully qualified class name (FQCN) with a public no-arg constructor, or
- a Spring bean name of a
HeaderValidator. The lookup order is controlled byxently.api.headers.validator.source(see below).
-
xently.api.headers.validator.source— optional, enum controlling how validator strings are resolved:FQCN— use FQCN onlyBeanDefinition— use Spring bean name onlyFQCNB4BeanDefinition— try FQCN, then fall back to bean name (default)BeanDefinitionB4FQCN— try bean name, then fall back to FQCN
If you don’t configure anything, these headers are validated by default (can be extended/overridden):
- X-FeatureCode (optional)
- X-FeatureName (required)
- X-ServiceCode (required)
- X-ServiceName (required)
- X-ServiceSubCategory (optional)
- X-MinorServiceVersion (required) — regex
v?\d+(\.\d+){0,2}(case-insensitive) - X-ChannelCategory (required)
- X-ChannelCode (required)
- X-ChannelName (required)
- X-RouteCode (optional)
- X-TimeStamp (optional) — epoch seconds validated by
EpochTimestampValidator - X-ServiceMode (optional)
- X-SubscriberEvents (optional)
- X-CallBackURL (optional) — must match HTTP(S) URL (simple regex)
Example: adding/overriding headers (demo-web; for WebFlux, bean/package names will differ slightly)
demo-web/src/main/resources/application.properties shows how to:
- Add new headers (required and optional)
- Provide a custom validator by FQCN
- Override a default header with a validator provided via Spring bean name
xently:
api:
headers:
validator:
source: beandefinitionb4fqcn
validation:
headers:
- header-name: X-Additional-Required
- header-name: X-Additional-Optional
required: false
- header-name: X-Additional-Custom-Validator
required: false
validator: CustomValidator # Spring bean name
- header-name: X-Timestamp # Override the default X-TimeStamp header to accept ISO-8601 instead
required: false
validator: co.ke.xently.demoweb.validators.ISO8601TimestampHeaderValidatorxently.api.headers.validator.source=beandefinitionb4fqcn
xently.api.headers.validation.headers.[0].header-name=X-Additional-Required
xently.api.headers.validation.headers.[1].header-name=X-Additional-Optional
xently.api.headers.validation.headers.[1].required=false
xently.api.headers.validation.headers.[2].header-name=X-Additional-Custom-Validator
xently.api.headers.validation.headers.[2].required=false
# Spring bean name
xently.api.headers.validation.headers.[2].validator=CustomValidator
# Override the default X-TimeStamp header to accept ISO-8601 instead
xently.api.headers.validation.headers.[3].header-name=X-Timestamp
xently.api.headers.validation.headers.[3].required=false
xently.api.headers.validation.headers.[3].validator=validators.co.ke.xently.demoweb.ISO8601TimestampHeaderValidatorAll properties can be supplied via environment variables using Spring Boot’s relaxed binding rules. Examples:
xently.api.headers.validator.source→XENTLY_API_HEADERS_VALIDATION_VALIDATOR_SOURCExently.api.headers.validation.headers.[0].header-name→XENTLY_API_HEADERS_VALIDATION_HEADERS_0__HEADER_NAME
Note the conversion rules: dots to underscores, list indexes like [0] to _0_ (often written as 0__ to separate
levels), and kebab-case keys to upper snake case.
Implement the
HeaderValidator
interface.
You can provide validators by:
- FQCN: set
validator=com.example.MyValidator(class must have a public no-arg constructor), or - Bean name: declare
@Component class MyValidator implements HeaderValidatorand setvalidator=MyValidator.
The starters expose a simple extension point via the
PayloadConverter
interface to help you:
- Customise the body returned when header validation fails.
- Convert the deserialized request-body into a "request payload" type that can carry a
messageID.
To override, define your own Spring bean of type PayloadConverter in your application. Because the default bean is
created with @ConditionalOnMissingBean, your bean will take precedence automatically.
Minimal example (works for both MVC and WebFlux apps):
import co.ke.xently.common.headers.exceptions.HeadersValidationException;
import co.ke.xently.common.utils.converter.PayloadConverter;
import co.ke.xently.common.utils.dto.Request;
import co.ke.xently.common.utils.dto.ResponsePayload;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
@Configuration
class CustomPayloadConverterConfig {
@Bean
PayloadConverter payloadConverter() {
return new PayloadConverter() {
@Override
public @NonNull Object convertToRequestPayload(@NonNull Object payload) {
// Optionally map your DTO to Request so the starters can extract messageID.
// If payload is already a Request, just return it.
return payload; // replace with mapping logic if needed
}
@Override
public @NonNull Object convertToHeaderValidationErrorResponse(
@NonNull ResponsePayload<?> defaultBody,
@NonNull HeadersValidationException exception) {
// Optionally transform the default error payload into a custom structure.
return defaultBody; // or return your own DTO/map
}
};
}
}On validation failure header validation throws HeadersValidationException. The starter’s error handlers convert it
to a 400 response similar to:
{
"conversationID": "...",
"messageID": "...",
"messageCode": "4000453",
"messageDescription": "Invalid or missing request headers",
"statusCode": "0",
"statusDescription": "Failed",
"additionalData": [],
"errorInfo": [
{
"errorCode": "<header-name>",
"errorDescription": "<descriptive-error-message>"
}
]
}