Skip to main content

Convention-Endpoints Mixin Pack

The Juneau REST server ships with a four-class convention-endpoints mixin pack in org.apache.juneau.rest.server.convention covering the small set of de-facto-standard URLs that every public-facing service eventually has to wire up by hand: the browser favicon, the SEO contracts (robots.txt, sitemap.xml), a JSON deployment-introspection endpoint, and the RFC 8615 /.well-known/* discovery root. Each mixin is a single-purpose @Rest-annotated resource designed to be grafted into your servlet via @Rest(mixins=...) — pick the URLs you want, configure them via a @Bean factory, and leave the rest unmounted.

The four mixins at a glance​

MixinDefault pathsDefault bodyWhy it exists
FaviconMixin/favicon.icoJuneau-branded juneau-favicon.ico shipped on the framework classpath.Stops every browser from logging a 404 on /favicon.ico.
SeoMixin/robots.txt, /sitemap.xmlUser-agent: *\nDisallow: /\n (deny-all) and an empty <urlset>.RFC 9309 + sitemaps.org compliance. Deny-all default is intentional — opt into indexing explicitly.
VersionMixin/version, /info, /aboutJSON map sourced from META-INF/MANIFEST.MF, git.properties, and the running JVM version.Deployment introspection. All three URLs return the same payload (they are synonyms).
WellKnownMixin/.well-known/security.txtNone — returns 404 Not Found until the importer configures a body.RFC 9116 security.txt discovery; absence is itself meaningful, so v1 ships with no placeholder.

All four endpoints carry @OpSwagger(ignore = true) on their handlers — they show up in the runtime URL space but are excluded from any Swagger / OpenAPI spec generated by the api-docs mixin pack. Convention URLs are not API-meaningful, and shipping them in the spec just clutters the contract.

Composing the pack​

Each mixin is independent — drop in only the ones you need. The examples below build on plain RestServlet. If you're building on BasicRestServlet/BasicRestResource instead, note that FaviconMixin (along with ErrorMixin, HtdocMixin, and StatsMixin) is already baked in by default — re-declaring mixins=FaviconMixin.class on the subclass is redundant but harmless, since the mixin walk collects mixins in parent-to-child order and dedupes by class identity (see Mixin Sub-Contexts for the walk details).

The plan-A composition mounts all four with builder-driven configuration:

@Rest(
path = "/api",
mixins = {
FaviconMixin.class,
SeoMixin.class,
VersionMixin.class,
WellKnownMixin.class
}
)
public class ApiResource extends RestServlet {

@RestGet("/items") public List<Item> items() { ... }

@Bean FaviconMixin favicon() {
return FaviconMixin.create().bytes(myLogoBytes).build();
}

@Bean SeoMixin seo() {
return SeoMixin.create()
.robotsAllow("*", "/")
.sitemapEntry("https://example.com/api/items")
.build();
}

@Bean VersionMixin version() {
// Defaults read MANIFEST.MF + git.properties + JVM version when no entries are set;
// override programmatically here:
return VersionMixin.create()
.entry("name", "my-app")
.entry("version", "1.2.3")
.fromGitProperties()
.fromJavaVersion()
.build();
}

@Bean WellKnownMixin wellKnown() {
return WellKnownMixin.create()
.securityTxt("Contact: security@example.com\nExpires: 2027-01-01T00:00:00Z\n")
.build();
}
}

The framework's mixin walk picks up each @Bean <MixinClass> factory before falling back to no-arg construction (see Mixin Sub-Contexts for the underlying lookup), so the host controls the configuration end-to-end without subclassing. Mixins without a @Bean factory get default behavior: FaviconMixin serves the framework icon; SeoMixin returns deny-all robots and an empty sitemap; VersionMixin reads MANIFEST.MF + git.properties from the framework classloader; WellKnownMixin returns 404 on /.well-known/security.txt.

Standalone deployment​

Each mixin is a fully-fledged @Rest-annotated resource and can also be subclassed and mounted as its own top-level servlet:

@Rest(paths = {"/favicon.ico"})
public class FaviconResource extends FaviconMixin { }

Both deployment styles (mixin into an existing servlet vs. mount as a sibling servlet) work the same way under Spring Boot and under the Jetty microservice — the VersionMixin_JettyMicroservice_Test and VersionMixin_Springboot_Test parity tests cover both paths end-to-end.

Per-mixin notes​

FaviconMixin​

  • Cache-Control: max-age=2592000, public (30 days) by default — favicons rarely change and browsers re-fetch frequently when uncached. Override via Builder.cacheControl(String).
  • Content-Type: image/x-icon.
  • Builder methods: bytes(byte[]), classpath(String), cacheControl(String). bytes and classpath are mutually exclusive — whichever is set last wins. A classpath path that resolves to a missing resource silently falls back to the framework default.

SeoMixin​

  • /robots.txt content type: text/plain; charset=UTF-8. Default body is the deny-all User-agent: *\nDisallow: /\n. Replace via Builder.robotsAllow(String, String...) / robotsDisallow(...) for rule-based policies, or robotsTxt(String) for a fully-formed body (useful when you need Sitemap: / Crawl-delay: directives the rule builder doesn't model).
  • /sitemap.xml content type: application/xml; charset=UTF-8. Default body is an empty <urlset>. Add entries via Builder.sitemapEntry(String url) (URL-only) or the four-arg form with lastmod, changefreq, and priority.

VersionMixin​

  • Content-Type: application/json — format-pinned via RestResponse.getDirectWriter("application/json") so the endpoint serves JSON even on a vanilla RestServlet host that hasn't wired up JSON serializers explicitly.
  • All three URLs (/version, /info, /about) currently return the same JSON payload. Future iterations may differentiate (e.g. /info = condensed, /about = full).
  • Default builder chain: fromManifest() + fromGitProperties() + fromJavaVersion(). The manifest reader prefers a META-INF/MANIFEST.MF whose Implementation-Title is set; if no candidate has one, the first manifest found wins. Spring Boot fat-jar caveat: the no-arg reader uses the framework classloader, so to see the importer's app manifest under a fat jar call Builder.fromManifest(importerClassLoader) (or Builder.fromManifest(Manifest) with a pre-loaded manifest).
  • Programmatic override: Builder.entry(String, String) / Builder.entries(Map). Calling any builder method suppresses the default chain — you opt into each fromXxx() call you want.

WellKnownMixin​

  • Content-Type: text/plain; charset=UTF-8.
  • No default body — per RFC 9116 the file's presence is itself meaningful. If you don't call Builder.securityTxt(String), the endpoint returns 404 Not Found. Document loudly to your ops team.
  • The class reserves room for future /.well-known/* entries (OIDC discovery, change-password, etc.); v1 ships only security.txt to stay scoped.

See also​