<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://mehmandarov.com/tag/jakarta-ee/feed.xml" rel="self" type="application/atom+xml"/><link href="https://mehmandarov.com/tag/jakarta-ee/" rel="alternate" type="text/html"/><updated>2026-04-19T09:50:00+02:00</updated><id>https://mehmandarov.com/tag/jakarta-ee/feed.xml</id><title type="html">Rustam Mehmandarov - tag: jakarta ee</title><subtitle type="text">Posts tagged &quot;jakarta ee&quot; on Rustam Mehmandarov.</subtitle><author><name>Rustam Mehmandarov</name></author><entry><title type="html">API versioning in Java using JAX-RS with Jakarta EE and MicroProfile</title><link href="https://mehmandarov.com/api-versioning/" rel="alternate" type="text/html" title="API versioning in Java using JAX-RS with Jakarta EE and MicroProfile"/><published>2026-04-19T09:50:00+02:00</published><updated>2026-04-19T09:50:00+02:00</updated><id>https://mehmandarov.com/api-versioning</id><content type="html" xml:base="https://mehmandarov.com/api-versioning/"><![CDATA[<p><em>Creating APIs and maintaining them over time means often that we need to version them. We will be looking into several ways of doing so in Java using JAX-RS, while building our API end-points using Jakarta EE and MicroProfile. This post was inspired by my talk &#8220;API = Some REST and HTTP, right? RIGHT?!&#8221;</em></p>

<ul>
  <li><a href="#introduction">Introduction</a></li>
  <li><a href="#why-versioning">Why Versioning?</a></li>
  <li><a href="#show-me-the-code">Show Me The CODE!</a></li>
  <li><a href="#1-url-versioning">1. URL Versioning</a></li>
  <li><a href="#2-header-versioning">2. Header Versioning</a></li>
  <li><a href="#3-media-type-versioning-content-negotiation">3. Media Type Versioning</a></li>
  <li><a href="#4-request-parameter-versioning">4. Request Parameter Versioning</a></li>
  <li><a href="#5-bonus-combining-strategies---transparent-uri-rewriting-enterprise-pattern">5. Bonus: Combining Strategies</a></li>
  <li><a href="#6-end-point-deprecation">6. End-Point Deprecation</a></li>
  <li><a href="#summary-comparison">Summary Comparison</a></li>
  <li><a href="#conclusion">Conclusion</a></li>
  <li><a href="#whats-next">What&#8217;s Next?</a></li>
</ul>

<hr />

<h2 id="introduction">Introduction</h2>

<p>When working with APIs over time we would often need to make some changes to end-point definitions &#8211; like adding or deleting resources or changing the attributes for a resource. To ensure backwards compatibility, we often have to introduce <em>versioning</em> for our APIs. APIs, like all software, evolve. You might be adding optional fields or introducing a breaking change. At some point, you will need versioning to support coexistence of the old and new consumers.</p>

<p>However, versioning the API endpoint introduces a question of how this should be done. In this post, we&#8217;ll explore <strong>several common API versioning strategies</strong>, using Jakarta EE and Java.</p>

<blockquote>
  <p>&#128161; Note: There is no silver bullet &#8211; instead, we&#8217;ll explore <strong>pros, cons, and real-world fit</strong>.</p>
</blockquote>

<h2 id="why-versioning">Why Versioning?</h2>

<p>Why not just change the API?<br />
Because breaking contracts is dangerous &#8212; clients may not update in sync, and you&#8217;ll break production consumers.</p>

<p>Versioning allows you to:</p>
<ul>
  <li>Support legacy clients</li>
  <li>Introduce new features safely</li>
  <li>Deprecate responsibly</li>
</ul>

<blockquote>
  <p>&#9888;&#65039; <strong>Caution</strong>: Versioning can cause &#8220;version explosion.&#8221; Each version increases long-term maintenance cost &#8211; aka <em>technical debt</em>.</p>
</blockquote>

<p><strong>Best Practice</strong>: Prefer <em>backward-compatible changes</em> (e.g., adding fields) whenever possible. To mitigate risks, it&#8217;s important to follow best practices for versioning and provide clear documentation and migration paths for users. Also, remember to <em>deprecate</em> old versions to minimize maintenance efforts.</p>

<h2 id="show-me-the-code">Show me the CODE!</h2>

<p>I have created a repository called <a href="https://github.com/mehmandarov/randomstrings/">Random Strings</a> to showcase various concepts. For this blogpost, I would recommend having a look at <a href="https://github.com/mehmandarov/randomstrings/blob/master/src/main/java/com/mehmandarov/randomstrings/apidemo/RandomStringsAPIDemoController.java"><code class="language-plaintext highlighter-rouge">RandomStringsAPIDemoController.java</code></a> and <a href="https://github.com/mehmandarov/randomstrings/blob/master/request_examples.http"><code class="language-plaintext highlighter-rouge">request_examples.http</code></a>. You will find all the info on building and running the code in the repo&#8217;s <code class="language-plaintext highlighter-rouge">README.md</code> file. Each section below will contain &#8220;How to call it&#8221; part with an example using <code class="language-plaintext highlighter-rouge">curl</code> or HTTP-files, and will be based on this repo.</p>

<hr />

<h2 id="1-url-versioning">1. URL Versioning</h2>

<h3 id="what-it-looks-like">What it looks like</h3>
<p>A version appears directly in the URI path. If your API is at <code class="language-plaintext highlighter-rouge">https://example.com/api</code>, and the current version is version 1, the URL for a resource might look like this: <code class="language-plaintext highlighter-rouge">https://example.com/api/v1/resource</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"/v2/"</span><span class="o">)</span>
<span class="nd">@Produces</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Response</span> <span class="nf">getV2</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">status</span><span class="o">(</span><span class="nc">Response</span><span class="o">.</span><span class="na">Status</span><span class="o">.</span><span class="na">NOT_IMPLEMENTED</span><span class="o">)</span>
        <span class="o">.</span><span class="na">entity</span><span class="o">(</span><span class="s">"This v2 using *path versioning* of the API is not implemented."</span><span class="o">)</span>
        <span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="how-to-call-it">How to call it</h3>

<p><strong>cURL:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:8080/api/rnd/v2/ <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</code></pre></div></div>

<p><strong>HTTP Request (<code class="language-plaintext highlighter-rouge">.http</code> file):</strong></p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET http://localhost:8080/api/rnd/v2/
Accept: application/json
</span></code></pre></div></div>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li>Simple and intuitive. Visible.</li>
  <li>Easy to test (e.g., with curl or Postman directly in a browser).</li>
  <li>Plays well with gateways and reverse proxies.</li>
  <li>Clear visual distinction between versions.</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li>Pollutes the URI with versioning logic.</li>
  <li>Breaks REST&#8217;s principle of stable resource identifiers.</li>
  <li>Clients have to update URLs when migrating.</li>
  <li>Risk of accumulating too many legacy versions.</li>
  <li>Can result in cluttered and difficult-to-read URLs if there are multiple versions of the API.</li>
</ul>

<p><strong>&#128269; However:</strong> Despite its REST purism flaw, URL versioning is extremely practical and widely adopted.</p>

<h2 id="2-header-versioning">2. Header Versioning</h2>

<h3 id="what-it-looks-like-1">What it looks like</h3>
<p>Client specifies version in a custom HTTP header (e.g., <code class="language-plaintext highlighter-rouge">Accept-Version</code>, <code class="language-plaintext highlighter-rouge">X-API-Version</code>, etc.):</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Path</span><span class="o">(</span><span class="s">"/hi2"</span><span class="o">)</span>
<span class="nd">@GET</span>
<span class="nd">@Produces</span><span class="o">({</span><span class="s">"application/json"</span><span class="o">})</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">entryPoint2</span><span class="o">(</span><span class="nd">@HeaderParam</span><span class="o">(</span><span class="s">"Accept-Version"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">apiVersion</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">apiVersion</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">apiVersion</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
        <span class="k">return</span> <span class="s">"Default unversioned endpoint hit."</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="nc">String</span> <span class="n">message</span> <span class="o">=</span> <span class="s">"Versioned: Using custom headers. Using version: "</span> <span class="o">+</span> <span class="n">apiVersion</span> <span class="o">+</span><span class="s">"."</span><span class="o">;</span>
    <span class="k">return</span> <span class="n">message</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="how-to-call-it-1">How to call it</h3>

<p><em>Note: This is for demo purposes only. It has to have a different URL than the regular API; otherwise, it will also intercept calls that do not contain the <code class="language-plaintext highlighter-rouge">Accept-Version</code> header.</em></p>

<p><strong>cURL:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:8080/api/rnd/versioned/ <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept-Version: 2"</span>
</code></pre></div></div>

<p><strong>HTTP Request (<code class="language-plaintext highlighter-rouge">.http</code> file):</strong></p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET http://localhost:8080/api/rnd/versioned/
Accept: application/json
Accept-Version: 2
</span></code></pre></div></div>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li>Keeps URL structure clean and predictable.</li>
  <li>Closer to HTTP semantics (headers = metadata).</li>
  <li>Allows centralized versioning logic in filters/interceptors.</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li>Not self-descriptive &#8212; clients must &#8220;know the secret handshake&#8221;.</li>
  <li>Poor discoverability (not visible in browser without tools).</li>
  <li>Breaks caching in some proxies/CDNs unless explicitly configured.</li>
  <li>Adds complexity to tooling and testing.</li>
</ul>

<p><strong>&#9888;&#65039; Challenge:</strong> Header versioning can feel &#8220;invisible&#8221; and cause developer confusion if not well-documented.</p>

<h2 id="3-media-type-versioning-content-negotiation">3. Media Type Versioning (Content Negotiation)</h2>

<h3 id="what-it-looks-like-2">What it looks like</h3>
<p>Client specifies version via a custom media type in the <code class="language-plaintext highlighter-rouge">Accept</code> header. This is sometimes called <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation">Content Negotiation</a> versioning.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Accept: application/hi.v3+json
</code></pre></div></div>

<p>In Jakarta EE:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Path</span><span class="o">(</span><span class="s">"/hi"</span><span class="o">)</span>
<span class="nd">@GET</span>
<span class="nd">@Produces</span><span class="o">({</span><span class="s">"application/hi.v3+json"</span><span class="o">,</span> <span class="s">"application/hi.v4+json"</span><span class="o">})</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">entryPoint</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">URISyntaxException</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">message</span> <span class="o">=</span> <span class="s">"Versioned: Hai there!"</span><span class="o">;</span>
    <span class="k">return</span> <span class="n">message</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="how-to-call-it-2">How to call it</h3>

<p>You can request different versions (e.g., v3, v4, v5) by updating the media type:</p>

<p><strong>cURL:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:8080/api/rnd/ <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/rnd.v3+json"</span>
</code></pre></div></div>

<p><strong>HTTP Request (<code class="language-plaintext highlighter-rouge">.http</code> file):</strong></p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET http://localhost:8080/api/rnd/
Accept: application/rnd.v3+json
</span></code></pre></div></div>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li>Very REST-compliant: changes representation, not resource.</li>
  <li>URI remains stable.</li>
  <li>Supports richer format negotiation (e.g., XML, HAL, etc.).</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li>Requires strict control over media types.</li>
  <li>Not all clients/tooling handle custom media types well.</li>
  <li>Breaks with some reverse proxies and middleware that don&#8217;t forward full Accept headers.</li>
  <li>More work to configure content negotiation.</li>
</ul>

<p><strong>&#129514; Observation:</strong> Elegant in design, but rarely used consistently in real-world public APIs.</p>

<h2 id="4-request-parameter-versioning">4. Request Parameter Versioning</h2>

<h3 id="what-it-looks-like-3">What it looks like</h3>
<p><em>Technically</em>, it is also possible for the client to specify the version in a URL query parameter (e.g., <code class="language-plaintext highlighter-rouge">?version=2</code>). This, however, might not be a suggested strategy, in my opinion.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://example.com/api/resource?version<span class="o">=</span>2
</code></pre></div></div>

<h3 id="how-to-call-it-3">How to call it</h3>

<p><strong>cURL:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:8080/api/rnd?version<span class="o">=</span>2 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</code></pre></div></div>

<p><strong>HTTP Request (<code class="language-plaintext highlighter-rouge">.http</code> file):</strong></p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET http://localhost:8080/api/rnd?version=2
Accept: application/json
</span></code></pre></div></div>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li>Simplicity &amp; discoverability: Easy to test in a browser without specialized tools.</li>
  <li>Defaulting logic: Straightforward to implement &#8220;default to latest&#8221; if the parameter is omitted.</li>
  <li>Caching friendly: CDNs treat different query params as unique resources by default.</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li>URI Pollution: Mixes resource identification with technical metadata.</li>
  <li>Routing complexity: Routing based on query parameters usually requires custom middleware or manual logic inside the controller.</li>
  <li>Harder to generate clean, automated documentation (like OpenAPI) when multiple versions share the same path.</li>
</ul>

<h2 id="5-bonus-combining-strategies---transparent-uri-rewriting-enterprise-pattern">5. Bonus: Combining Strategies - Transparent URI Rewriting (Enterprise Pattern)</h2>

<p>In large enterprises, you might find that different clients have different needs. Some prefer the explicitness of URL versioning, while others require the clean URIs of Header versioning. You don&#8217;t have to choose just one&#8212;you can support both without duplicating your backend routing logic.</p>

<p>The common practice is to structure all your resource classes using <strong>URL versioning</strong> (e.g., <code class="language-plaintext highlighter-rouge">@Path("/v1/resource")</code>), but use a <strong><code class="language-plaintext highlighter-rouge">@PreMatching</code> Filter</strong> to intercept requests and transparently rewrite the URI if a client uses a header instead.</p>

<p>Here is what that looks like in Jakarta EE using a <code class="language-plaintext highlighter-rouge">ContainerRequestFilter</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Provider</span>
<span class="nd">@PreMatching</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">HeaderVersionFilter</span> <span class="kd">implements</span> <span class="nc">ContainerRequestFilter</span> <span class="o">{</span>

      <span class="nd">@Override</span>
      <span class="kd">public</span> <span class="kt">void</span> <span class="nf">filter</span><span class="o">(</span><span class="nc">ContainerRequestContext</span> <span class="n">ctx</span><span class="o">)</span> <span class="o">{</span>
          <span class="nc">String</span> <span class="n">path</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getUriInfo</span><span class="o">().</span><span class="na">getPath</span><span class="o">();</span>

          <span class="c1">// If the path is already versioned (e.g., starts with v1, v2), let it pass</span>
          <span class="k">if</span> <span class="o">(</span><span class="n">path</span><span class="o">.</span><span class="na">matches</span><span class="o">(</span><span class="s">"v\\d+(/.*)?"</span><span class="o">))</span> <span class="k">return</span><span class="o">;</span>

          <span class="c1">// Otherwise, check if the client provided a version header</span>
          <span class="nc">String</span> <span class="n">version</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getHeaderString</span><span class="o">(</span><span class="s">"X-API-Version"</span><span class="o">);</span>

          <span class="k">if</span> <span class="o">(</span><span class="n">version</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">version</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
              <span class="c1">// Transparently rewrite the URI internally to match our URL-based routes</span>
              <span class="nc">String</span> <span class="n">newPath</span> <span class="o">=</span> <span class="s">"v"</span> <span class="o">+</span> <span class="n">version</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="n">path</span><span class="o">;</span>
              <span class="no">URI</span> <span class="n">baseUri</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getUriInfo</span><span class="o">().</span><span class="na">getBaseUri</span><span class="o">();</span>
              <span class="no">URI</span> <span class="n">newUri</span> <span class="o">=</span> <span class="nc">UriBuilder</span><span class="o">.</span><span class="na">fromUri</span><span class="o">(</span><span class="n">baseUri</span><span class="o">).</span><span class="na">path</span><span class="o">(</span><span class="n">newPath</span><span class="o">).</span><span class="na">build</span><span class="o">();</span>

              <span class="n">ctx</span><span class="o">.</span><span class="na">setRequestUri</span><span class="o">(</span><span class="n">baseUri</span><span class="o">,</span> <span class="n">newUri</span><span class="o">);</span>
          <span class="o">}</span>
      <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li><strong>Ultimate Flexibility:</strong> Clients can use <code class="language-plaintext highlighter-rouge">http://api.example.com/v2/resource</code> OR <code class="language-plaintext highlighter-rouge">http://api.example.com/resource</code> with an <code class="language-plaintext highlighter-rouge">X-API-Version: 2</code> header.</li>
  <li><strong>Single Source of Truth:</strong> Your backend controllers only need to use <code class="language-plaintext highlighter-rouge">@Path("/v2/")</code>. You don&#8217;t have to write duplicate methods to handle both headers and paths.</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li><strong>Magic Routing:</strong> It introduces a layer of &#8220;magic&#8221; where the requested URI differs from the routed URI, which can briefly confuse new developers debugging the application.</li>
</ul>

<p><strong>&#128161; Want to know more?</strong> Read up on terms <em><code class="language-plaintext highlighter-rouge">Version Normalization</code></em> and <em><code class="language-plaintext highlighter-rouge">Internal Decoupling</code></em>.</p>

<h2 id="6-end-point-deprecation">6. End-Point Deprecation</h2>

<p>Eventually, you will need to retire old API versions. Remember: every old version you keep around is <em>technical debt</em> &#8212; it increases long-term maintenance cost. When deprecating an endpoint, consider the following best practices:</p>

<ol>
  <li><strong>Update the Docs:</strong> Use OpenAPI&#8217;s <code class="language-plaintext highlighter-rouge">@Operation</code> annotation to clearly mark it as deprecated.</li>
  <li><strong>Add <code class="language-plaintext highlighter-rouge">@Deprecated</code>:</strong> Use the Java <code class="language-plaintext highlighter-rouge">@Deprecated</code> annotation where necessary.</li>
  <li><strong>HTTP Redirects:</strong> Consider returning HTTP codes like <code class="language-plaintext highlighter-rouge">302 Found</code> or <code class="language-plaintext highlighter-rouge">301 Moved Permanently</code> after some time.</li>
  <li><strong>Add a Link header:</strong> Provide a link to the new version in the response headers.</li>
  <li><strong>Log / Count calls:</strong> Track usage (e.g., with MicroProfile <code class="language-plaintext highlighter-rouge">@Counted</code>) to know when it is safe to finally remove the endpoint.</li>
</ol>

<p>Here is a practical example in Jakarta EE showing how to deprecate an endpoint, add a <code class="language-plaintext highlighter-rouge">Link</code> header, and track metrics:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"v0.1/"</span><span class="o">)</span>
<span class="nd">@Produces</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
<span class="nd">@Operation</span><span class="o">(</span><span class="n">summary</span> <span class="o">=</span> <span class="s">"DEPRECATED. Use v2 now. Returns the adjective-noun pair"</span><span class="o">,</span>
           <span class="n">description</span> <span class="o">=</span> <span class="s">"Deprecated function. The pair of one random adjective and one random noun is returned as an array."</span><span class="o">)</span>
<span class="nd">@Counted</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"totalCountToRandomPairCalls_Versioned_Path_DEPRECATED"</span><span class="o">,</span>
         <span class="n">absolute</span> <span class="o">=</span> <span class="kc">true</span><span class="o">,</span>
         <span class="n">description</span> <span class="o">=</span> <span class="s">"Deprecated function call: Total number of calls to random string pairs."</span><span class="o">,</span>
         <span class="n">tags</span> <span class="o">=</span> <span class="o">{</span><span class="s">"calls=pairs"</span><span class="o">})</span>
<span class="nd">@Deprecated</span>
<span class="kd">public</span> <span class="nc">Response</span> <span class="nf">getRndStringPathDeprecated</span><span class="o">()</span> <span class="o">{</span>
    <span class="no">URI</span> <span class="n">newVersionURI</span> <span class="o">=</span> <span class="nc">UriBuilder</span><span class="o">.</span><span class="na">fromUri</span><span class="o">(</span><span class="s">"/api/rnd/v2/"</span><span class="o">).</span><span class="na">build</span><span class="o">();</span>
    <span class="nc">Link</span> <span class="n">newVersionLink</span> <span class="o">=</span> <span class="nc">Link</span><span class="o">.</span><span class="na">fromUri</span><span class="o">(</span><span class="n">newVersionURI</span><span class="o">).</span><span class="na">rel</span><span class="o">(</span><span class="s">"alternate"</span><span class="o">).</span><span class="na">build</span><span class="o">();</span>
    <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="s">"Deprecated response"</span><span class="o">,</span> <span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
            <span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="n">jakarta</span><span class="o">.</span><span class="na">ws</span><span class="o">.</span><span class="na">rs</span><span class="o">.</span><span class="na">core</span><span class="o">.</span><span class="na">HttpHeaders</span><span class="o">.</span><span class="na">LINK</span><span class="o">,</span> <span class="n">newVersionLink</span><span class="o">.</span><span class="na">toString</span><span class="o">())</span>
            <span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"X-API-Version"</span><span class="o">,</span> <span class="s">"0.1"</span><span class="o">)</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="how-to-call-it-deprecated-endpoint">How to call it (Deprecated endpoint)</h3>

<p><strong>cURL:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:8080/api/rnd/v0.1/ <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</code></pre></div></div>

<p><strong>HTTP Request (<code class="language-plaintext highlighter-rouge">.http</code> file):</strong></p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET http://localhost:8080/api/rnd/v0.1/
Accept: application/json
</span></code></pre></div></div>

<h2 id="summary-comparison">Summary Comparison</h2>

<p>The following table summarizes all the different routing strategies implemented in the <a href="https://github.com/mehmandarov/randomstrings/">demo project</a>, illustrating how the HTTP method, path, and headers combine to invoke the correct Java method. The method names refer to the methods in <a href="https://github.com/mehmandarov/randomstrings/blob/master/src/main/java/com/mehmandarov/randomstrings/apidemo/RandomStringsAPIDemoController.java"><code class="language-plaintext highlighter-rouge">RandomStringsAPIDemoController.java</code></a> (or <code class="language-plaintext highlighter-rouge">RandomStringsController.java</code>):</p>

<table>
  <thead>
    <tr>
      <th>HTTP Method</th>
      <th>Path</th>
      <th>Headers</th>
      <th>Method Invoked</th>
      <th>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd</code></td>
      <td><em>None</em></td>
      <td><code class="language-plaintext highlighter-rouge">getRndString()</code></td>
      <td>Default (unversioned) endpoint</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd</code></td>
      <td><code class="language-plaintext highlighter-rouge">Accept: application/json</code></td>
      <td><code class="language-plaintext highlighter-rouge">getRndString()</code></td>
      <td>Standard media type</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd/v2/</code></td>
      <td><em>Any</em></td>
      <td><code class="language-plaintext highlighter-rouge">getRndStringV2path()</code></td>
      <td>Demo for <strong>path-based</strong> versioning</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd/versioned</code></td>
      <td><em>None</em></td>
      <td><code class="language-plaintext highlighter-rouge">getRndStringV2Header()</code></td>
      <td>Fallback to <code class="language-plaintext highlighter-rouge">getRndString()</code> if header is missing</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd/versioned</code></td>
      <td><code class="language-plaintext highlighter-rouge">Accept-Version: 2</code></td>
      <td><code class="language-plaintext highlighter-rouge">getRndStringV2Header()</code></td>
      <td><strong>Header-based</strong> versioning</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd</code></td>
      <td><code class="language-plaintext highlighter-rouge">Accept: application/rnd.v3+json</code></td>
      <td><code class="language-plaintext highlighter-rouge">getRndStringV3V4MediaType()</code></td>
      <td><strong>Media type versioning</strong> &#8212; v3</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd</code></td>
      <td><code class="language-plaintext highlighter-rouge">Accept: application/rnd.v4+json</code></td>
      <td><code class="language-plaintext highlighter-rouge">getRndStringV3V4MediaType()</code></td>
      <td><strong>Media type versioning</strong> &#8212; v4</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GET</code></td>
      <td><code class="language-plaintext highlighter-rouge">/rnd</code></td>
      <td><code class="language-plaintext highlighter-rouge">Accept: application/rnd.v5+json</code></td>
      <td><code class="language-plaintext highlighter-rouge">getRndStringV5MediaType()</code></td>
      <td><strong>Media type versioning</strong> &#8212; v5</td>
    </tr>
  </tbody>
</table>

<h2 id="conclusion">Conclusion</h2>

<p>There is no single correct approach to API versioning. For most teams and public APIs, <strong>URL versioning</strong> is good enough&#8212;it&#8217;s visible, easy to test, and plays well with existing tooling. However, you might want to use <strong>header versioning</strong> if your APIs are primarily consumed by internal services or SDKs that can abstract away the complexity. Reserve <strong>media type versioning</strong> for hypermedia-rich or REST-purist APIs, and only if your tooling supports it end-to-end.</p>

<p>Consider who your consumers are, whether your API is public or internal, your infrastructure maturity, and your team&#8217;s ability to support multiple versions.</p>

<h2 id="whats-next">What&#8217;s Next?</h2>

<p>Versioning is just one part of building robust REST APIs. If you want to dive deeper, have a look at the <a href="https://github.com/mehmandarov/api-guide-java">API Guide for Java</a> repository and the slides in the <a href="https://github.com/mehmandarov/api-guide-java/tree/main/presentation">presentation folder</a>. They cover documentation with OpenAPI, security best practices (like RBAC and JWT integration), advanced patterns (pagination, async APIs), and going beyond REST with gRPC and GraphQL.</p>

<p><strong><em>Happy coding!</em></strong></p>

<hr />]]></content><author><name>Rustam Mehmandarov</name></author><summary type="html">Exploring common API versioning strategies in Java using JAX-RS with Jakarta EE and MicroProfile &#8211; URL, header, and media type versioning &#8211; with pros, cons, and practical code examples.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mehmandarov.com/assets/images/posts-images/container-ship.jpeg"/><category term="blog"/><category term="english"/><category term="java"/><category term="architecture"/><category term="api"/><category term="jakarta ee"/><category term="microprofile"/><category term="jax-rs"/><category term="openapi"/></entry><entry><title type="html">MicroProfile &#8211; Part 1: Defining End-Points</title><link href="https://mehmandarov.com/microprofile-101-part1/" rel="alternate" type="text/html" title="MicroProfile &#8211; Part 1: Defining End-Points"/><published>2019-07-29T07:25:00+02:00</published><updated>2019-07-29T07:25:00+02:00</updated><id>https://mehmandarov.com/microprofile-101-part1</id><content type="html" xml:base="https://mehmandarov.com/microprofile-101-part1/"><![CDATA[<p><em>Part 1: End-points in MicroProfile. This is a part of a series of posts to help you get started with microservices in MicroProfile and showing off some of the features it brings to the table.</em></p>

<blockquote>
  <p><strong>Update (April 2026):</strong> This post has been refreshed to use the modern <code class="language-plaintext highlighter-rouge">jakarta.*</code> namespace introduced in Jakarta EE 9+. If you are working with an older codebase that still uses the legacy <code class="language-plaintext highlighter-rouge">javax.*</code> namespace, the concepts described here apply identically &#8212; only the package prefix differs.</p>
</blockquote>

<ul>
  <li><a href="#intro">Intro</a></li>
  <li><a href="#getting-started">Getting started</a></li>
  <li><a href="#defining-end-points">Defining End-Points</a>
    <ul>
      <li><a href="#a-regular-json-end-point">A (Regular) JSON End-Point</a></li>
      <li><a href="#an-end-point-returning-an-image">An End-Point Returning an Image</a></li>
      <li><a href="#an-end-point-returning-a-pdf">An End-Point Returning a PDF</a></li>
    </ul>
  </li>
  <li><a href="#whats-next">What&#8217;s Next?</a></li>
</ul>

<hr />

<h2 id="intro">Intro</h2>

<p>In my previous two posts, I have been describing parts of a system for &#8220;checking-in&#8221; to a location using QR codes in Java. We started with <a href="/generating-qr-codes-with-secure-hashes-using-java/">generating QR codes</a>, followed by <a href="/generating-pdf-files-using-java/">generating PDF files</a>.</p>

<p>Now, I would like to focus on building microservices around that functionality. We will be creating a few HTTP end-points built with <a href="https://microprofile.io/">MicroProfile</a>. I will be using the next few posts as an opportunity to show off some of the features that you will be getting out of the box or with minimal effort using MicroProfile.</p>

<h2 id="getting-started">Getting started</h2>

<p><em>(Assuming that you have Git, Java 9+, and Maven installed.)</em></p>

<p>Since we will continue using the QR code generator project to showcase various features of MicroProfile, it might be a good idea to familiarize yourself with the code. You might want to start with taking a look at my previous two posts that explain the code for <a href="/generating-qr-codes-with-secure-hashes-using-java/">generating QR codes</a>, and <a href="/generating-pdf-files-using-java/">generating PDF files</a> in detail.</p>

<p>Now you can clone the <a href="https://github.com/mehmandarov/microprofile-qrcodes/">project</a> and examine the Maven dependencies in the <a href="https://github.com/mehmandarov/microprofile-qrcodes/blob/master/pom.xml"><code class="language-plaintext highlighter-rouge">pom.xml</code></a> file, as well as any other MicroProfile related dependencies.</p>

<p>After cloning and opening the project in your favorite IDE, build it (again, assuming that you have Java and Maven installed) with the following command in a terminal:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>mvn clean package</code></pre></figure>

<p>When the project is done building and you have got a <code class="language-plaintext highlighter-rouge">Build Success</code> from Maven, you can run the project to make sure everything runs fine:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>java <span class="nt">-jar</span> target/qrcreator.jar</code></pre></figure>

<p>In a few seconds it takes for the app server to start-up, you should be able to access the starter page at <a href="http://localhost:8181/qrcreator/index.html"><code class="language-plaintext highlighter-rouge">http://localhost:8181/qrcreator/index.html</code></a>.</p>

<h2 id="defining-end-points">Defining End-Points</h2>

<p>One of the most obvious features any microservice needs is an end-point to receive requests and respond with some kind of data back. Let&#8217;s have a closer look into how this is done in MicroProfile. First of all, we will need to define the application path that serves as the base URI for all resource URIs (think of it as a &#8220;root&#8221; URL) and make sure that the class where it is defined extends <code class="language-plaintext highlighter-rouge">jakarta.ws.rs.core.Application</code>, like in <a href="https://github.com/mehmandarov/microprofile-qrcodes/blob/master/src/main/java/com/mehmandarov/qrcreator/ApplicationEntryPoint.java"><code class="language-plaintext highlighter-rouge">ApplicationEntryPoint</code></a> class here:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="nn">com.mehmandarov.qrcreator</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">jakarta.ws.rs.ApplicationPath</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">jakarta.ws.rs.core.Application</span><span class="o">;</span>

<span class="cm">/**
 * Sets the application path that serves as the base URI for
 * all resource URIs provided by @Path annotation.
 */</span>
<span class="nd">@ApplicationPath</span><span class="o">(</span><span class="s">"/api"</span><span class="o">)</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ApplicationEntryPoint</span> <span class="kd">extends</span> <span class="nc">Application</span> <span class="o">{</span>
<span class="o">}</span></code></pre></figure>

<p>This will set up all the end-point URLs to start with <code class="language-plaintext highlighter-rouge">/api</code>, in our case <a href="http://localhost:8181/qrcreator/api/"><code class="language-plaintext highlighter-rouge">http://localhost:8181/qrcreator/api/</code></a>.</p>

<h4 id="a-regular-json-end-point">A (Regular) JSON End-Point</h4>

<p>Now, let&#8217;s define some endpoints. We will start with a most regular kind &#8211; a JSON end-point. This is probably the most common end-point you will encounter.</p>

<p>We will start with creating one that would respond to requests sent to <a href="http://localhost:8181/qrcreator/api/somestring/json"><code class="language-plaintext highlighter-rouge">/api/somestring/json</code></a>. Note that as you can see from the code below, <code class="language-plaintext highlighter-rouge">@Path</code> defines <code class="language-plaintext highlighter-rouge">somestring</code> in the URL as an <code class="language-plaintext highlighter-rouge">id</code> that is passed on as an argument to the <code class="language-plaintext highlighter-rouge">createIdKeyTuple</code> method.</p>

<p>We will also define a type of a request (in this case it is a <code class="language-plaintext highlighter-rouge">GET</code> request) and specify that it will be returning a JSON document with <code class="language-plaintext highlighter-rouge">@Produces</code> annotation.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"{id}/json"</span><span class="o">)</span>
<span class="nd">@Produces</span><span class="o">(</span><span class="s">"application/json"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Response</span> <span class="nf">createIdKeyTuple</span><span class="o">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
    <span class="o">...</span>
<span class="o">}</span></code></pre></figure>

<p>Now, that we are done with annotations for the method, let&#8217;s have a look at the code for this method, that defines a JSON end-point in the <a href="https://github.com/mehmandarov/microprofile-qrcodes/blob/master/src/main/java/com/mehmandarov/qrcreator/QRController.java"><code class="language-plaintext highlighter-rouge">QRController</code></a> class:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"{id}/json"</span><span class="o">)</span>
<span class="nd">@Produces</span><span class="o">(</span><span class="s">"application/json"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Response</span> <span class="nf">createIdKeyTuple</span><span class="o">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">nameKeyTuple</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="n">nameKeyTuple</span> <span class="o">=</span> <span class="n">qrCodeContentsSupplier</span><span class="o">.</span><span class="na">getQRCodeContents</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
        <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">nameKeyTuple</span><span class="o">).</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvalidKeySpecException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NoSuchAlgorithmException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">serverError</span><span class="o">().</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>

<p>Note that in this code we have only simple exception handling that makes sure we return a correct HTTP status code &#8211; OK (200) on a success and server error (500) on an internal error. You can later add other HTTP codes based on your needs.</p>

<h4 id="an-end-point-returning-an-image">An End-Point Returning an Image</h4>

<p>Returning an image instead of a JSON document is quite similar to the code we have already seen. Here we will have to pay attention to three aspects:</p>

<ul>
  <li>Different MIME type defined in <code class="language-plaintext highlighter-rouge">@Produces</code> annotation: <code class="language-plaintext highlighter-rouge">@Produces("image/png")</code></li>
  <li>Additional elements in the response header that let you control how the created file is displayed in the browser, i.e. shown in the browser &#8220;inline&#8221;, or made available through a download dialog &#8211; <code class="language-plaintext highlighter-rouge">"Content-Disposition", "inline;"</code> (see specs for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition"><code class="language-plaintext highlighter-rouge">Content-Disposition</code></a> for more details)</li>
  <li>Additional elements in the response header that let you control the name for the created file: <code class="language-plaintext highlighter-rouge">filename=\"" + id + ".png\"</code></li>
</ul>

<p>Let&#8217;s have a look at the whole method:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"{id}"</span><span class="o">)</span>
<span class="nd">@Produces</span><span class="o">(</span><span class="s">"image/png"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Response</span> <span class="nf">createQR</span><span class="o">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="kt">byte</span><span class="o">[]</span> <span class="n">imageData</span> <span class="o">=</span> <span class="n">qrCodeSupplier</span><span class="o">.</span><span class="na">qrCodeGenerator</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
        <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">imageData</span><span class="o">)</span>
            <span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"Content-Disposition"</span><span class="o">,</span> <span class="s">"inline; filename=\""</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span> <span class="s">".png\""</span><span class="o">)</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">WriterException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NoSuchAlgorithmException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvalidKeySpecException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">serverError</span><span class="o">().</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>

<p>It is worth noting that the number of <code class="language-plaintext highlighter-rouge">catch</code> statements in the <code class="language-plaintext highlighter-rouge">try...catch</code> clause will vary and depend on the number and type of exceptions that can be thrown by the underlying methods.</p>

<h4 id="an-end-point-returning-a-pdf">An End-Point Returning a PDF</h4>

<p>The last method for today &#8211; defining an end-point for returning PDF files &#8211; is nearly identical to the one we used for returning images, except for one thing:</p>

<ul>
  <li>Different MIME type and explicit file encoding in <code class="language-plaintext highlighter-rouge">@Produces</code>: <code class="language-plaintext highlighter-rouge">@Produces("application/pdf; charset=utf-8")</code>.</li>
</ul>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"{id}/pdf"</span><span class="o">)</span>
<span class="nd">@Produces</span><span class="o">(</span><span class="s">"application/pdf; charset=utf-8"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Response</span> <span class="nf">createQRPDF</span><span class="o">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="kt">byte</span><span class="o">[]</span> <span class="n">pdfDocument</span> <span class="o">=</span> <span class="n">pdfDocumentSupplier</span><span class="o">.</span><span class="na">pdfDocumentGenerator</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
        <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">pdfDocument</span><span class="o">)</span>
            <span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"Content-Disposition"</span><span class="o">,</span> <span class="s">"inline; filename=\""</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span> <span class="s">".pdf\""</span><span class="o">)</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">WriterException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NoSuchAlgorithmException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvalidKeySpecException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">serverError</span><span class="o">().</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>

<p>Of course, there are also obvious differences in the contents of the <code class="language-plaintext highlighter-rouge">byte[]</code> array, but I consider that being outside the scope of this post &#8211; you can study those differences on your own.</p>

<h2 id="whats-next">What&#8217;s Next?</h2>

<p>Here we have seen how easy it is to define an end-point that can return documents with various MIME types. In the next posts, we will be taking a closer look at things like how you can equip your end-points with metrics, provide auto-generated documentation based on OpenAPI, and add more resilience with fail-over and circuit-breakers.</p>

<hr />]]></content><author><name>Rustam Mehmandarov</name></author><summary type="html">Getting started with MicroProfile &#8211; defining end-points for JSON, images, and PDF responses.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mehmandarov.com/assets/images/posts-images/lego-record-shop.jpg"/><category term="blog"/><category term="java"/><category term="microservices"/><category term="microprofile"/><category term="jakarta ee"/><category term="english"/></entry></feed>
