Configuration
JsonApi4j is configured differently depending on your web framework. All three integrations support the same set of properties — only the format and loading mechanism differ.
Configuration by Framework
Spring Boot uses standard application.yaml or application.properties with the jsonapi4j prefix. Properties are bound via @ConfigurationProperties.
application.yaml:
jsonapi4j:
rootPath: /jsonapi
meta:
enabled: true
validation:
maxNumberFilterParams: 5
maxElementsInFilterParam: 20
resourceIdMaxLength: 64
limitMaxValue: 100
maxElementsInIncludeParam: 10
maxElementsInSortByParam: 5
ac:
enabled: true
failOnMisconfiguration: false
sf:
enabled: true
requestedFieldsDontExistMode: SPARSE_ALL_FIELDS
cd:
enabled: true
maxHops: 3
maxIncludedResources: 100
mapping:
users: http://localhost:8080/jsonapi
countries: http://localhost:8080/jsonapi
oas:
enabled: true
info:
title: My API
version: 1.0.0Spring Boot's standard property resolution applies: application.yaml, application.properties, environment variables, system properties, and profile-specific files all work as expected.
All JsonApi4j beans are registered with @ConditionalOnMissingBean, so you can override any bean by defining your own in a @Configuration class.
Quarkus uses application.properties with the same jsonapi4j prefix. Properties are bound at build time via @ConfigMapping.
application.properties:
jsonapi4j.rootPath=/jsonapi
jsonapi4j.meta.enabled=true
jsonapi4j.validation.maxNumberFilterParams=5
jsonapi4j.validation.maxElementsInFilterParam=20
jsonapi4j.validation.resourceIdMaxLength=64
jsonapi4j.validation.limitMaxValue=100
jsonapi4j.validation.maxElementsInIncludeParam=10
jsonapi4j.validation.maxElementsInSortByParam=5
jsonapi4j.ac.enabled=true
jsonapi4j.ac.failOnMisconfiguration=false
jsonapi4j.sf.enabled=true
jsonapi4j.sf.requestedFieldsDontExistMode=SPARSE_ALL_FIELDS
jsonapi4j.cd.enabled=true
jsonapi4j.cd.maxHops=3
jsonapi4j.cd.maxIncludedResources=100
jsonapi4j.cd.mapping.users=http://localhost:8080/jsonapi
jsonapi4j.cd.mapping.countries=http://localhost:8080/jsonapi
jsonapi4j.oas.enabled=true
jsonapi4j.oas.info.title=My API
jsonapi4j.oas.info.version=1.0.0Quarkus uses Smallrye Config for property resolution. List-valued properties accept both comma-separated (jsonapi4j.cd.propagation=FIELDS,CUSTOM_QUERY_PARAMS,HEADERS) and indexed (jsonapi4j.cd.propagation[0]=FIELDS) syntax — both bind to the same list, and the Meta API config resource renders either form identically as a JSON array. The same holds for Spring Boot's relaxed binding. All JsonApi4j CDI beans use @DefaultBean, so you can override them with your own @Produces methods.
For plain Servlet applications, JsonApi4j loads configuration from a YAML or JSON file. No framework-specific property binding is used.
jsonapi4j.yaml (on the classpath):
rootPath: /jsonapi
meta:
enabled: true
validation:
maxNumberFilterParams: 5
maxElementsInFilterParam: 20
resourceIdMaxLength: 64
limitMaxValue: 100
maxElementsInIncludeParam: 10
maxElementsInSortByParam: 5
ac:
enabled: true
failOnMisconfiguration: false
sf:
enabled: true
requestedFieldsDontExistMode: SPARSE_ALL_FIELDS
cd:
enabled: true
maxHops: 3
maxIncludedResources: 100
mapping:
users: http://localhost:8080/jsonapi
countries: http://localhost:8080/jsonapi
oas:
enabled: true
info:
title: My API
version: 1.0.0Note: the Servlet config file uses the property structure directly (no jsonapi4j prefix). Both jsonapi4j.yaml and jsonapi4j.json formats are supported.
Config Loading Order (Servlet)
For Spring Boot and Quarkus, configuration loading follows each framework’s standard rules (profiles, environment variables, system properties, etc.).
For the Servlet integration, JsonApi4j resolves configuration in the following priority order:
| Priority | Source | Example |
|---|---|---|
| 1 | Servlet context attribute | Set programmatically or by Spring Boot / Quarkus auto-configuration |
| 2 | System property jsonapi4j.config |
-Djsonapi4j.config=/path/to/config.yaml |
| 3 | Environment variable JSONAPI4J_CONFIG |
export JSONAPI4J_CONFIG=/path/to/config.yaml |
| 4 | Servlet init parameter jsonapi4j.config |
Set in web.xml or programmatically via context.setInitParameter(...) |
| 5 | Classpath defaults | jsonapi4j.yaml or jsonapi4j.json on the classpath |
Core Properties
| Property | Default | Description |
|---|---|---|
jsonapi4j.rootPath |
/jsonapi |
Root path for all JsonApi4j endpoints. All resource and relationship URLs are served under this path. |
jsonapi4j.meta.enabled |
false |
Enables the built-in Meta API — a runtime introspection endpoint exposing the app’s resources, relationships, operations, plugins, and effective config. Opt-in. |
Validation Properties
JsonApi4j includes a built-in structural validator that enforces limits on common request parameters. These properties configure the thresholds used by the built-in validator.
| Property | Default | Description |
|---|---|---|
jsonapi4j.validation.maxNumberFilterParams |
5 |
Maximum number of distinct filter[*] query parameters allowed in a single request. |
jsonapi4j.validation.maxElementsInFilterParam |
20 |
Maximum number of comma-separated values within a single filter[name] parameter. |
jsonapi4j.validation.resourceIdMaxLength |
64 |
Maximum allowed length of a resource ID string. |
jsonapi4j.validation.limitMaxValue |
100 |
Maximum allowed value for the page[limit] pagination parameter. |
jsonapi4j.validation.maxElementsInIncludeParam |
10 |
Maximum number of relationship paths in the include query parameter. |
jsonapi4j.validation.maxElementsInSortByParam |
5 |
Maximum number of fields in the sort query parameter. |
Plugin Properties
Each plugin adds its own properties under the jsonapi4j namespace. Refer to the corresponding plugin page for the full list:
| Plugin | Prefix | Documentation |
|---|---|---|
| Access Control | jsonapi4j.ac.* |
Access Control Plugin |
| Sparse Fieldsets | jsonapi4j.sf.* |
Sparse Fieldsets Plugin |
| OpenAPI | jsonapi4j.oas.* |
OpenAPI Plugin |
| Compound Documents | jsonapi4j.cd.* |
Compound Documents Plugin |
A config file with everything in it
Each sample application ships a configuration file listing every property with its default, so the whole surface is visible in one place rather than assembled from tables:
| Framework | File |
|---|---|
| Spring Boot | examples/jsonapi4j-springboot-sampleapp/src/main/resources/application.yaml |
| Quarkus | examples/jsonapi4j-quarkus-sampleapp/src/main/resources/application.properties |
| Servlet | examples/jsonapi4j-servlet-sampleapp/src/main/resources/jsonapi4j.yaml |
Copy one and delete what you do not need — every value shown is the default, so removing a line changes nothing.
Overriding Beans
Both Spring Boot and Quarkus allow you to override any default bean provided by JsonApi4j.
Spring Boot — define a bean of the same type in a @Configuration class. JsonApi4j uses @ConditionalOnMissingBean on all defaults.
@Configuration
public class CustomConfig {
@Bean
public ExecutorService jsonApi4jExecutorService() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
Quarkus — provide a @Produces method. JsonApi4j beans are annotated with @DefaultBean, so your producer takes precedence.
public class CustomConfig {
@Produces
@Singleton
public ExecutorService jsonApi4jExecutorService() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
Servlet — set custom instances as ServletContext attributes before the framework initializes. JsonApi4j checks the servlet context for known attributes (e.g. JsonApi4j, PrincipalResolver, ErrorHandlerFactoriesRegistry, ExecutorService, ObjectMapper) and uses them if present; otherwise it falls back to defaults.
// Build and register a custom JsonApi4j instance
JsonApi4j jsonApi4j = JsonApi4j.builder()
.domainRegistry(domainRegistry)
.operationsRegistry(operationsRegistry)
.plugins(plugins)
.executor(Executors.newVirtualThreadPerTaskExecutor())
.build();
servletContext.setAttribute(JsonApi4jServletContainerInitializer.JSONAPI4J_ATT_NAME, jsonApi4j);
// Override PrincipalResolver
servletContext.setAttribute(JsonApi4jServletContainerInitializer.PRINCIPAL_RESOLVER_ATT_NAME, myCustomPrincipalResolver);
See the servlet sample app for a complete example.
Overriding the PrincipalResolver
PrincipalResolver is the bean most applications end up overriding, because it decides who the caller is.
The default reads X-Authenticated-* headers; declaring your own bean replaces it through the same
mechanisms shown above.
Spring Boot — read claims Spring Security has already verified:
@Bean
public PrincipalResolver jsonapi4jPrincipalResolver() {
return SpringSecurityPrincipalResolver.withEntitlementsClaim("entitlements");
}
Quarkus — read claims Quarkus OIDC has already verified:
@Produces
@Singleton
public PrincipalResolver principalResolver(JsonWebToken jwt) {
return QuarkusJwtPrincipalResolver.withEntitlementsClaim(jwt, "entitlements");
}
Both are opt-in, so an existing header-based setup keeps working untouched after an upgrade. See Principal Resolution for the full set of resolvers, claim mapping, and the security trade-offs between them.
Overriding the AccessControlEvaluator
AccessControlEvaluator decides every access control requirement for the whole application. The default,
DefaultAccessControlEvaluator, evaluates authenticated, entitlements, scopes, ownership and
policy — each must pass.
Reach for a policy first. A single rule that the declarative requirements cannot express belongs in an
AccessPolicy on the one annotation that needs
it. Replacing the evaluator makes you responsible for all five requirement types on every annotated
element in the application, which is rarely what you want:
@AccessControl(policy = @AccessControlPolicy(MyRule.class)) // one rule, one place
Replace the evaluator only when you are changing how evaluation itself works — decisions delegated to an external authorization service, an audit record for every decision, or caching.
Extend the default rather than starting from scratch, so the requirements you are not changing keep working:
public class AuditingAccessControlEvaluator extends DefaultAccessControlEvaluator {
@Override
public EvaluationResult evaluateInboundRequirements(AccessControlContext context,
AccessControlModel accessControlModel) {
EvaluationResult result = super.evaluateInboundRequirements(context, accessControlModel);
auditLog.record(context.principal().authenticatedUserId(), context.operation(), result.granted());
return result;
}
}
EvaluationResult carries the decision together with the ErrorCode to report when access is refused, so
a denial can say more than “forbidden” — the default evaluator reports INSUFFICIENT_ENTITLEMENTS or
INSUFFICIENT_SCOPES where those are what failed. Both come from the same evaluation, so the reason can
never disagree with the decision.
The code is an ErrorCode, not a fixed set of framework-defined reasons, so an
evaluator can report something meaningful in its own domain:
if (subscriptionExpired(context)) {
return EvaluationResult.denied(MyErrorCodes.SUBSCRIPTION_EXPIRED);
}
return EvaluationResult.allowed();
Spring Boot — the default is @ConditionalOnMissingBean, so your bean wins:
@Bean
public AccessControlEvaluator jsonapi4jAccessControlEvaluator() {
return new AuditingAccessControlEvaluator();
}
Quarkus — the default is a @DefaultBean, so your producer takes precedence:
@Produces
@Singleton
public AccessControlEvaluator accessControlEvaluator() {
return new AuditingAccessControlEvaluator();
}
Servlet — unlike PrincipalResolver, the evaluator is not read from a ServletContext attribute. Pass it
to the plugin when you build the plugin list:
new JsonApiAccessControlPlugin(
new AuditingAccessControlEvaluator(),
DefaultAcProperties.toAcProperties(jsonApi4jPropertiesRaw)
)
Both evaluation methods receive an
AccessControlContext carrying the principal, the operation,
the request and — outbound — the resource being emitted.
For a complete configuration example with all plugins enabled, see the sample application configs: