<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: paohaijiao</title>
    <description>The latest articles on DEV Community by paohaijiao (@paohaijiao).</description>
    <link>https://dev.to/paohaijiao</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3962449%2F3e44ee44-5431-4b0d-9999-3faebfff31b5.jpeg</url>
      <title>DEV Community: paohaijiao</title>
      <link>https://dev.to/paohaijiao</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paohaijiao"/>
    <language>en</language>
    <item>
      <title>From Copy-Pasting curl Commands to Building My Own Java Parser (jquick-curl)</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Thu, 17 Sep 2026 23:08:30 +0000</pubDate>
      <link>https://dev.to/paohaijiao/from-copy-pasting-curl-commands-to-building-my-own-java-parser-jquick-curl-ff0</link>
      <guid>https://dev.to/paohaijiao/from-copy-pasting-curl-commands-to-building-my-own-java-parser-jquick-curl-ff0</guid>
      <description>&lt;p&gt;&lt;em&gt;How a QA handoff turned into a weekend rabbit hole and eventually an open-source library&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; java, opensource, http, curl, backend&lt;/p&gt;




&lt;h2&gt;
  
  
  The Slack message that started it all
&lt;/h2&gt;

&lt;p&gt;It's 4:47 PM on a Thursday. QA drops this in the channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.internal.example.com/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-Trace-Id: 8f14e45f'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"sku":"A-1001","count":2,"warehouse":"SH-03"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Can you check why this fails in staging but works for me locally?"&lt;/p&gt;

&lt;p&gt;Cool. Easy. I just need to reproduce this in our Java service. So I open the controller, and I start... translating.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-X POST&lt;/code&gt; → okay, &lt;code&gt;HttpMethod.POST&lt;/code&gt;. &lt;code&gt;-H&lt;/code&gt; lines → I build a &lt;code&gt;HttpHeaders&lt;/code&gt; object, add each one by hand, careful not to typo &lt;code&gt;Authorization&lt;/code&gt;. &lt;code&gt;-d&lt;/code&gt; → I need a &lt;code&gt;MultiValueMap&lt;/code&gt; or just serialize the JSON string directly into the body, depends on which client we're using this week. Then I remember we're on &lt;code&gt;RestTemplate&lt;/code&gt; in this module but &lt;code&gt;OkHttp&lt;/code&gt; in that other module, so I have to remember which boilerplate applies here.&lt;/p&gt;

&lt;p&gt;Ten minutes later I've got a Java snippet that &lt;em&gt;should&lt;/em&gt; be equivalent to the curl command. I run it. It fails differently than staging did. I stare at both side by side for a solid two minutes before I notice I dropped a header. Of course I did — I was hand-transcribing eight lines of shell syntax into a completely different language's HTTP client API, header by header, like a monk copying a manuscript.&lt;/p&gt;

&lt;p&gt;And this wasn't a one-off. This was Tuesday. This was every integration week. Front-end hands me curl from their network tab, API docs ship curl examples, QA pastes curl into bug reports — curl is the &lt;em&gt;lingua franca&lt;/em&gt; everyone actually uses to describe an HTTP call. Nobody writes a bug report in &lt;code&gt;OkHttpClient&lt;/code&gt; Java syntax. They all just hit copy on the "Copy as cURL" button in devtools.&lt;/p&gt;

&lt;p&gt;So I was translating the same universal language into Java, by hand, multiple times a day. And translating things by hand, repeatedly, is exactly the kind of problem that eventually makes you angry enough to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I didn't just reach for an existing tool
&lt;/h2&gt;

&lt;p&gt;Before writing a single line, I did what everyone does — I went looking for something that already solved this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Just shell out to the real &lt;code&gt;curl&lt;/code&gt; binary.&lt;/strong&gt; Java's &lt;code&gt;ProcessBuilder&lt;/code&gt; can absolutely call the system &lt;code&gt;curl&lt;/code&gt; command and read stdout. I actually prototyped this first. It "worked," but I hated it almost immediately. It meant every machine running my service — dev laptops, CI runners, Docker images, production hosts — now had a hard dependency on a &lt;em&gt;specific&lt;/em&gt; curl binary being installed and on PATH, with a specific version, potentially with different flag support across platforms. On Windows it was a mess. In a minimal Alpine-based Docker image, &lt;code&gt;curl&lt;/code&gt; isn't even there by default. I didn't want my Java library to depend on the presence of a totally separate program written in C. It's not portable, it's fragile, and honestly it felt wrong to spawn an OS process just to make an HTTP call from inside the JVM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Use one of those "curl to code" online converters.&lt;/strong&gt; These are fine for a single one-off translation, but they don't solve my actual problem — I don't want a Java code &lt;em&gt;generator&lt;/em&gt; I paste output from once. I want the curl string itself to be the source of truth, permanently, so that when QA sends me an updated curl next sprint, I don't regenerate and re-paste new boilerplate again. I want to keep the curl command in my codebase, unmodified, and have it stay useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: Just write the RestTemplate/OkHttp code properly, like an adult.&lt;/strong&gt; Yeah, obviously that's what I'd been doing. But it's exactly the boilerplate-per-endpoint problem — headers, method, body serialization, timeouts — repeated for every single API call in the codebase. None of that boilerplate teaches anyone anything; it's just friction between "I have a working curl command" and "I have a working Java call."&lt;/p&gt;

&lt;p&gt;What I actually wanted was something that didn't exist: a way to parse curl syntax &lt;em&gt;natively inside the JVM&lt;/em&gt;, with no external process, and turn it straight into an executable HTTP call. Not a code generator. Not a wrapper around a shell binary. An actual parser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing jquick-curl
&lt;/h2&gt;

&lt;p&gt;The core idea, once I let myself think about it properly, was almost stupidly simple: &lt;strong&gt;the curl command a person hands me should just work, unmodified, as the request definition.&lt;/strong&gt; I shouldn't have to rewrite it into anything. I should be able to paste it straight from Chrome's "Copy as cURL," slap it on a Java interface method, and call that method.&lt;/p&gt;

&lt;p&gt;That meant I needed a real grammar for curl syntax, not a pile of regex and &lt;code&gt;String.split()&lt;/code&gt; hacks (I tried that first, for about a day, before giving up — curl's quoting rules, escaped characters inside &lt;code&gt;-d&lt;/code&gt; payloads, and mixed short/long flags make naive string splitting fall apart fast). So I reached for &lt;strong&gt;ANTLR4&lt;/strong&gt; to define a proper grammar for the subset of curl I cared about: methods, headers, cookies, body flags, form/multipart uploads, auth, proxy settings, redirects. ANTLR runs entirely inside the JVM — no external process, no shelling out, no platform dependency. It parses the curl string into a syntax tree I can walk and turn into a structured request object. That single decision is what let me kill the "depend on a real curl binary" idea for good: jquick-curl runs identically on Windows, Linux, and Mac, and doesn't care whether curl is even installed on the machine.&lt;/p&gt;

&lt;p&gt;Once I had a parsed request, I needed something to actually execute it, and there was no reason to reinvent that part — &lt;strong&gt;OkHttp&lt;/strong&gt; underneath handles the real transport: connection pooling, timeouts, retries, redirects, interceptors. jquick-curl's job is purely to bridge "curl syntax" to "OkHttp request," not to replace a mature HTTP engine.&lt;/p&gt;

&lt;p&gt;Then came the part that made it feel like an actual tool instead of a toy parser: &lt;strong&gt;how do you attach a curl string to your codebase in a way that fits how Java developers actually work?&lt;/strong&gt; I ended up supporting two styles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An annotation, &lt;code&gt;@JCurlCommand&lt;/code&gt;, that you drop directly on an interface method — you paste the curl string as-is, and a dynamic proxy (&lt;code&gt;JCurlInvoker.createProxy(...)&lt;/code&gt;) turns the interface into a real client at runtime.&lt;/li&gt;
&lt;li&gt;An XML definition file (&lt;code&gt;apis.xml&lt;/code&gt;), for teams who'd rather centralize every API's curl template in one place instead of scattering them across annotations — handy when non-Java people (QA, API owners) are the ones maintaining the request definitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there I kept adding the things that came up naturally once I started actually using it for real work: &lt;code&gt;${name}&lt;/code&gt; / &lt;code&gt;#{name}&lt;/code&gt; placeholder substitution so a curl template isn't hard-coded per environment, &lt;code&gt;&amp;lt;if test="..."&amp;gt;&lt;/code&gt; conditional rendering in the XML flavor for headers that only apply sometimes, cookie jar support (&lt;code&gt;-b @file&lt;/code&gt;, &lt;code&gt;-c file&lt;/code&gt;) because our session-based APIs needed it, multipart upload and file download via &lt;code&gt;-F&lt;/code&gt; and &lt;code&gt;-o&lt;/code&gt;, global interceptors for things like injecting a bearer token, and retry/timeout/connection-pool config through one &lt;code&gt;JQuickCurlConfig&lt;/code&gt; singleton instead of per-call setup.&lt;/p&gt;

&lt;p&gt;None of that was planned up front, honestly — it grew out of "oh, I need this for the thing I'm debugging right now" over a few weekends.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually looks like
&lt;/h2&gt;

&lt;p&gt;Add the dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.paohaijiao&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jquick-curl&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.5.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take that curl command from earlier — the one from the Slack message — and just... put it on an interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST https://api.internal.example.com/orders "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"-H 'Authorization: Bearer ${token}' "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"-H 'X-Trace-Id: ${traceId}' "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"-d '{\"sku\":\"A-1001\",\"count\":2,\"warehouse\":\"SH-03\"}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Demo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API_TOKEN"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"traceId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"8f14e45f"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;HttpHeaders&lt;/code&gt;. No manually building a request body. No deciding which client's boilerplate applies this week. The curl string &lt;em&gt;is&lt;/em&gt; the request definition — I literally paste what QA sent me, swap the hardcoded secrets for &lt;code&gt;${token}&lt;/code&gt; placeholders, and I have a working method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'd be lying if I said this solves everything
&lt;/h2&gt;

&lt;p&gt;I want to be straight about this, because I've seen enough "this library changes everything" posts to be allergic to writing one myself.&lt;/p&gt;

&lt;p&gt;jquick-curl is &lt;strong&gt;not&lt;/strong&gt; trying to replace RestTemplate or OkHttp for general-purpose HTTP client code in your application. If you're building a service that needs fine-grained, programmatic control over request construction — conditional logic that's too complex for a template string, streaming request bodies, custom &lt;code&gt;RequestBody&lt;/code&gt; implementations, deep integration with Spring's ecosystem (&lt;code&gt;WebClient&lt;/code&gt;, reactive pipelines, circuit breakers via Resilience4j, etc.) — you're still better off writing that in actual OkHttp or RestTemplate code. jquick-curl is a thin layer that turns curl syntax into an OkHttp call; it's not trying to out-feature a mature HTTP client.&lt;/p&gt;

&lt;p&gt;Where it genuinely earns its place, in my experience, is a fairly specific niche: &lt;strong&gt;reproducing and calling APIs that are described to you as curl commands&lt;/strong&gt; — QA bug reports, third-party API docs that only ship curl examples, internal service-to-service calls you're debugging by pasting from a browser's network tab, or integration test suites where "does this curl still work" is literally the assertion you want to make. It's also handy for quick internal tooling where you want request definitions to be editable by non-Java teammates via the XML config, without touching Java code.&lt;/p&gt;

&lt;p&gt;The honest limitations right now: the curl grammar covers a solid, tested subset of options (method, headers, cookies, data variants, form/multipart, basic auth, redirects, proxy, &lt;code&gt;-k&lt;/code&gt;/&lt;code&gt;-v&lt;/code&gt;/&lt;code&gt;-s&lt;/code&gt;, HTTP/2) — but curl has a &lt;em&gt;lot&lt;/em&gt; of flags, and I haven't chased every obscure one. If you hit something unsupported, the parser will tell you, not silently misbehave, but it's still a smaller surface than the real curl binary. Error messages from the ANTLR parser can also be more cryptic than I'd like when someone pastes malformed shell quoting — that's on my list to improve. And it's still a fairly young project, so I wouldn't bet a critical production path on it without your own test coverage around it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, break it, tell me
&lt;/h2&gt;

&lt;p&gt;Repo's here: &lt;strong&gt;&lt;a href="https://github.com/paohaijiao/jquick-curl" rel="noopener noreferrer"&gt;github.com/paohaijiao/jquick-curl&lt;/a&gt;&lt;/strong&gt; — Apache 2.0, PRs welcome, and &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; has the setup if you want to poke at the ANTLR grammar yourself.&lt;/p&gt;

&lt;p&gt;Two things I'm genuinely curious about from other backend folks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do you hit the same "curl handed to me, translate it by hand" friction, or does your team already have some internal convention that avoids this? I'd love to know if this is a me-problem or an us-problem.&lt;/li&gt;
&lt;li&gt;If you &lt;em&gt;have&lt;/em&gt; rolled your own curl-parsing tool before (I have to believe someone has), what broke first — quoting edge cases, multipart handling, something else?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Drop it in the comments — I read all of them, and half the feature list above exists because someone told me what they needed.&lt;/p&gt;




&lt;p&gt;Want this saved as a downloadable Markdown file for dev.to, or is copy-pasting from here fine?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>showdev</category>
      <category>rest</category>
      <category>java</category>
    </item>
    <item>
      <title>Interceptors That Actually Help: Request Logging and Automatic Bearer-Token Injection</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Wed, 16 Sep 2026 22:52:12 +0000</pubDate>
      <link>https://dev.to/paohaijiao/interceptors-that-actually-help-request-logging-and-automatic-bearer-token-injection-1bk5</link>
      <guid>https://dev.to/paohaijiao/interceptors-that-actually-help-request-logging-and-automatic-bearer-token-injection-1bk5</guid>
      <description>&lt;h1&gt;
  
  
  Interceptors That Actually Help: Request Logging and Automatic Bearer-Token Injection
&lt;/h1&gt;

&lt;p&gt;Cross-cutting HTTP concerns — logging, authentication headers, request IDs, metrics — should never be hand-edited into every request. JQuickCurl rides on OkHttp's mature interceptor pipeline, and because interceptors are wired into the &lt;strong&gt;global configuration&lt;/strong&gt;, a single registration upgrades every curl command in your application.&lt;/p&gt;

&lt;p&gt;In this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;What an interceptor sees and can do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The built-in &lt;code&gt;JLoggingInterceptor&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing a request/response logging interceptor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auto-injecting a &lt;code&gt;Bearer&lt;/code&gt; token from your secret store.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Interceptor Model in One Paragraph
&lt;/h2&gt;

&lt;p&gt;An OkHttp &lt;code&gt;Interceptor&lt;/code&gt; wraps a call: it receives the outgoing &lt;code&gt;Request&lt;/code&gt;, may modify it, calls &lt;code&gt;chain.proceed(...)&lt;/code&gt;, and then sees the returned &lt;code&gt;Response&lt;/code&gt;. Registering interceptors happens on the global config, so both annotation-mode and XML-mode commands pass through them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Interceptor&lt;/span&gt; &lt;span class="n"&gt;myInterceptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// outgoing&lt;/span&gt;
    &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proceed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// perform + get response&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;};&lt;/span&gt;

&lt;span class="nc"&gt;JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myInterceptor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;addNetworkInterceptor(...)&lt;/code&gt; registers at the network layer (after redirects/retries) when you need to observe the real wire traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-In Logging: &lt;code&gt;JLoggingInterceptor&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Out of the box, the config already registers a &lt;code&gt;JLoggingInterceptor&lt;/code&gt; at level &lt;code&gt;ALL&lt;/code&gt; — it measures each call and reports the elapsed time (and failures) through its console logger. If you want a quieter default, the same interceptor can be constructed with an explicit level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;JCurlLevelLog&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;NONE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BASIC&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;HEADERS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ALL&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.enums.JCurlLevelLog&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.interceptor.JLoggingInterceptor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.config.JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JLoggingInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JCurlLevelLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BASIC&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect output in the spirit of: &lt;code&gt;the request cost : 132 ms&lt;/code&gt;. Choose the noisiest level in development, &lt;code&gt;BASIC&lt;/code&gt; in staging, and keep an eye on token redaction (see below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a Purpose-Built Logging Interceptor
&lt;/h2&gt;

&lt;p&gt;Build one that logs method, URL, status, and duration — and be deliberate about what you &lt;em&gt;don't&lt;/em&gt; log (authorization headers!).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.Interceptor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.Request&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.Response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.ResponseBody&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CallLogInterceptor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Interceptor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;slf4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;slf4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CallLogInterceptor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Chain&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nanoTime&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proceed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;tookMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;concurrent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NANOSECONDS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMillis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nanoTime&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;bodyPreview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;bodyPreview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;safePreview&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;peekBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[http] {} {} -&amp;gt; {} in {} ms body={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;tookMs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bodyPreview&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;safePreview&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it next to the built-in one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CallLogInterceptor&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automatic Bearer-Token Injection
&lt;/h2&gt;

&lt;p&gt;Tokens expire, rotate, and come from secret managers — they don't belong in &lt;code&gt;@JCurlCommand&lt;/code&gt; strings. One interceptor can stamp &lt;em&gt;every&lt;/em&gt; outgoing request with the current token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.Interceptor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.Request&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.Response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearerAuthInterceptor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Interceptor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tokenSupplier&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;BearerAuthInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tokenSupplier&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tokenSupplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenSupplier&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Chain&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenSupplier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proceed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the token is fetched &lt;strong&gt;per request&lt;/strong&gt; via a &lt;code&gt;Supplier&lt;/code&gt;, rotating tokens are picked up automatically — no restart, no stale header. Wire it with your vault/environment source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.config.JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BearerAuthInterceptor&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API_TOKEN"&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a plain command stays clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/me'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;me&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// Authorization stamped by interceptor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same trick works for API keys, &lt;code&gt;X-Correlation-Id&lt;/code&gt;, tenant headers, and basic-auth pairs (Post 15).&lt;/p&gt;

&lt;h2&gt;
  
  
  Runnable Demo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.config.JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;InspectApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// httpbin.org/headers echoes every header the server received&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://httpbin.org/headers'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InterceptorDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;okhttp3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer demo-token-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proceed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InspectApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// "Authorization": "Bearer demo-token-123"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ordering and Pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Interceptors run in registration order (application interceptors before network interceptors).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Headers you add in an interceptor are applied &lt;strong&gt;on top of&lt;/strong&gt; whatever the curl string specified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never log or stash raw &lt;code&gt;Authorization&lt;/code&gt; values — hash or redact in logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep interceptors fast; they run on the request thread.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Interceptors are the clean seam for concerns that cut across every HTTP call. JQuickCurl comes with timing-oriented logging out of the box and accepts any OkHttp interceptor through &lt;code&gt;JQuickCurlConfig&lt;/code&gt;, so adding per-request Bearer tokens, correlation IDs, or metrics is a five-line change — while your curl commands stay declarative and secret-free.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;. Post 13 shows per-method timeout overrides with &lt;code&gt;@JTimeout&lt;/code&gt; when one stubborn endpoint needs different limits than the rest.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>java</category>
      <category>curl</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Downloads Without Boilerplate: Saving HTTP Responses to Local Disk with `--output`</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Tue, 15 Sep 2026 23:01:23 +0000</pubDate>
      <link>https://dev.to/paohaijiao/downloads-without-boilerplate-saving-http-responses-to-local-disk-with-output-2dh1</link>
      <guid>https://dev.to/paohaijiao/downloads-without-boilerplate-saving-http-responses-to-local-disk-with-output-2dh1</guid>
      <description>&lt;p&gt;Downloading a file in OkHttp means streaming &lt;code&gt;ResponseBody&lt;/code&gt;, choosing a sink, and closing resources correctly. In JQuickCurl, a download is just another curl command: add &lt;code&gt;--output './download/report.pdf'&lt;/code&gt; (or &lt;code&gt;-o&lt;/code&gt;) and the executor &lt;strong&gt;writes the response bytes to that path for you&lt;/strong&gt;. No InputStream juggling, no try-with-resources plumbing in your business code.&lt;/p&gt;

&lt;p&gt;This post covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;--output&lt;/code&gt; one-liner that saves straight to disk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Returning &lt;code&gt;byte[]&lt;/code&gt; and saving in Java for full control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Combined approaches and directory-handling notes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A runnable demo against a real image endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Approach 1: Let the Command Write the File
&lt;/h2&gt;

&lt;p&gt;The cleanest option. Add &lt;code&gt;--output &amp;lt;path&amp;gt;&lt;/code&gt; to the curl command; the file appears at the path when the call returns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;DownloadApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://httpbin.org/image/png' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"--output './download/diagram.png'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;downloadPng&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DownloadDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;DownloadApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DownloadApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;downloadPng&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Path&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./download/diagram.png"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Saved "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" bytes to "&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toAbsolutePath&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="c1"&gt;// -&amp;gt; Saved NNNN bytes to .../download/diagram.png&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happen: the executor writes &lt;code&gt;./download/diagram.png&lt;/code&gt;, and (because the method still declares &lt;code&gt;byte[]&lt;/code&gt;) you also receive the bytes — handy for checksumming or counting without an extra read. The directory must exist; JQuickCurl writes the file but does not create parent folders for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Return the Bytes and Save in Java
&lt;/h2&gt;

&lt;p&gt;If the storage location is business logic (a database column, an S3 bucket, a temp dir chosen per tenant), skip &lt;code&gt;--output&lt;/code&gt; and save the &lt;code&gt;byte[]&lt;/code&gt; yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;DownloadApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://httpbin.org/bytes/4096'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;downloadBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;downloadBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./download/random-4k.bin"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return types handled by the converter include &lt;code&gt;byte[]&lt;/code&gt;, and &lt;code&gt;JResult&lt;/code&gt; for raw access. Choose by payload size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Payload&lt;/th&gt;
&lt;th&gt;Return type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small / needs random access&lt;/td&gt;
&lt;td&gt;&lt;code&gt;byte[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium, file target known in command&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--output&lt;/code&gt; in command (any return)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need metadata too&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JResult&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Directory Setup and Path Conventions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pre-create the target directory (e.g., &lt;code&gt;./download&lt;/code&gt;) before the first call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prefer forward slashes — &lt;code&gt;--output './download/report.xlsx'&lt;/code&gt; is portable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On Windows, absolute paths like &lt;code&gt;'d://test//piett.xlsx'&lt;/code&gt; are the spelling used in the project's own XML samples; Java strings will treat &lt;code&gt;\&lt;/code&gt; as escapes, so &lt;code&gt;/&lt;/code&gt; is the low-friction choice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For versioned downloads, inject the file name via a variable:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/files/${fileId}' "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Accept: application/octet-stream' "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"--output './download/${fileId}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;downloadById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fileId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invoice-2026-03.pdf"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;downloadById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// file lands at ./download/invoice-2026-03.pdf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Complete Runnable Demo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MediaApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Image with a header check to prove headers are respected&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://httpbin.org/image/jpeg' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Accept: image/jpeg' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"--output './download/photo.jpg'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;grabPhoto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaveToDiskDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createDirectories&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./download"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;MediaApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;grabPhoto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" bytes"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Then verify the file on disk:&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;onDisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./download/photo.jpg"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"On disk      : "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;onDisk&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" bytes"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Downloads collapse into one declarative flag: &lt;code&gt;--output './path'&lt;/code&gt; writes the response to disk, while return types &lt;code&gt;byte[]&lt;/code&gt; keep the memory footprint in your control. Combined with &lt;code&gt;${...}&lt;/code&gt; variables for file names, JQuickCurl handles the whole download lifecycle with the syntax every engineer already knows.&lt;/p&gt;

&lt;p&gt;Code and docs live at &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;. Post 11 moves from per-request concerns to the process level: global tuning of connection pools, timeouts, retries, and redirects.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>java</category>
      <category>showdev</category>
      <category>curl</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Multipart Uploads Made Simple: Sending Files with curl's `-F` Flag in Java</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Mon, 14 Sep 2026 17:10:34 +0000</pubDate>
      <link>https://dev.to/paohaijiao/multipart-uploads-made-simple-sending-files-with-curls-f-flag-in-java-45fa</link>
      <guid>https://dev.to/paohaijiao/multipart-uploads-made-simple-sending-files-with-curls-f-flag-in-java-45fa</guid>
      <description>&lt;p&gt;Every admin console ends up with a file upload — Excel imports, avatar pictures, PDF attachments. Implementing multipart requests by hand in OkHttp or RestTemplate means building &lt;code&gt;MultipartBody&lt;/code&gt; pieces, computing &lt;code&gt;Content-Type&lt;/code&gt; boundaries, and leaking HTTP plumbing into service code. In JQuickCurl you simply keep curl's &lt;code&gt;-F file=@/path/to/file&lt;/code&gt; syntax: &lt;strong&gt;the upload command is the curl command.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-file upload.&lt;/li&gt;
&lt;li&gt;Multiple files in one request.&lt;/li&gt;
&lt;li&gt;Mixing files with plain form fields.&lt;/li&gt;
&lt;li&gt;Dynamic file paths through variables.&lt;/li&gt;
&lt;li&gt;A runnable example against httpbin's echo endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The curl Syntax Refresher
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# one file&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.example.com/upload &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'file=@./photo.png'&lt;/span&gt;

&lt;span class="c"&gt;# two files under the SAME field name&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.example.com/upload &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'files=@./a.pdf'&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'files=@./b.pdf'&lt;/span&gt;

&lt;span class="c"&gt;# a file plus regular form fields&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.example.com/upload &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'userId=123'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'note=hello'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'file=@./photo.png'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic marker is &lt;code&gt;@&lt;/code&gt;: &lt;code&gt;key=@path&lt;/code&gt; means &lt;em&gt;this value is a file on disk&lt;/em&gt;; &lt;code&gt;key=value&lt;/code&gt; without &lt;code&gt;@&lt;/code&gt; is a plain text field. JQuickCurl's parser understands exactly this convention and builds the corresponding multipart body over OkHttp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Single-File Upload
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UploadApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://httpbin.org/post' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'avatar=@./profile.png'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;uploadAvatar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UploadApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;uploadAvatar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// httpbin echoes back the "files" section&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;httpbin's &lt;code&gt;/post&lt;/code&gt; returns &lt;code&gt;"files": {"avatar": "&amp;lt;binary content&amp;gt;"}&lt;/code&gt;, which proves the multipart body really was transmitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: Multiple Files Under One Field
&lt;/h2&gt;

&lt;p&gt;Uploading several files at once simply repeats &lt;code&gt;-F&lt;/code&gt; with the same key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UploadApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://httpbin.org/post' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'documents=@./report-q1.pdf' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'documents=@./report-q2.pdf'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;uploadQuarterlyReports&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern works for a set of related files such as images or logs. For truly arbitrary numbers of files, pair the &lt;code&gt;-F&lt;/code&gt; command with a small loop that builds several commands or pick a fixed upper bound per call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: File + Ordinary Form Fields (The Importer)
&lt;/h2&gt;

&lt;p&gt;Real import endpoints rarely want a bare file — they want metadata alongside it. Combine fields and files freely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UploadApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://httpbin.org/post' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'uploader=${uploader}' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'description=${description}' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'spreadsheet=@./products.xlsx'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;importSpreadsheet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uploader"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"jane.doe"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"monthly product refresh"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spreadsheet"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./products.xlsx"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;importSpreadsheet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server receives form fields &lt;code&gt;uploader&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; plus file &lt;code&gt;spreadsheet&lt;/code&gt; — everything curl's multipart encoding supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 4: Dynamic File Paths
&lt;/h2&gt;

&lt;p&gt;Hard-coding a path in the annotation works, but real applications pick the file at runtime. Because &lt;code&gt;-F&lt;/code&gt; values participate in variable substitution, the path can come from &lt;code&gt;JQuickCurlReq&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://httpbin.org/post' "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'file=@${filePath}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;uploadFromPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"filePath"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"downloads/today/report.xlsx"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uploadFromPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Path tips: use forward slashes for portability; on Windows, backslashes inside a Java annotation string must be escaped (&lt;code&gt;\\\\&lt;/code&gt;) or prefer &lt;code&gt;/&lt;/code&gt;. Relative paths resolve against the process's working directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Complete Runnable Demo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MultiUploadApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://httpbin.org/post' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'owner=${owner}' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'primary=@./pom.xml' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-F 'secondary=@./README.md'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;uploadPair&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UploadDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MultiUploadApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MultiUploadApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"owner"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"team-core"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uploadPair&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Look for "form": { "owner": "team-core" } and&lt;/span&gt;
        &lt;span class="c1"&gt;// "files": { "primary": ..., "secondary": ... } in the echo.&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it from the project root so &lt;code&gt;pom.xml&lt;/code&gt; and &lt;code&gt;README.md&lt;/code&gt; exist — the demo then needs no extra setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Multipart Is the Wrong Tool
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Large files / resumable uploads:&lt;/strong&gt; multipart isn't streaming-friendly for gigabytes; consider chunked or presigned-URL uploads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny key/value pairs:&lt;/strong&gt; plain JSON or &lt;code&gt;--data-urlencode&lt;/code&gt; is lighter than multipart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files already on a server:&lt;/strong&gt; pass a URL and let the backend fetch it instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;JQuickCurl lets your team keep using the file-upload syntax they already know from curl and Postman. &lt;code&gt;-F 'key=@path'&lt;/code&gt; for files, &lt;code&gt;-F 'key=value'&lt;/code&gt; for fields, repeated &lt;code&gt;-F&lt;/code&gt; options for multi-file uploads, and &lt;code&gt;${path}&lt;/code&gt; variables for runtime-selected files. The multipart boundary choreography stays inside the library.&lt;/p&gt;

&lt;p&gt;That covers sending bytes up; Post 10 flips the direction and downloads remote files straight to local disk with curl's &lt;code&gt;--output&lt;/code&gt; flag.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>rest</category>
      <category>restapi</category>
      <category>http</category>
      <category>java</category>
    </item>
    <item>
      <title>Conditional Requests at Runtime: Rendering Parts of a curl Command with XML `&lt;if&gt;`</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Sun, 13 Sep 2026 23:19:04 +0000</pubDate>
      <link>https://dev.to/paohaijiao/conditional-requests-at-runtime-rendering-parts-of-a-curl-command-with-xml--g61</link>
      <guid>https://dev.to/paohaijiao/conditional-requests-at-runtime-rendering-parts-of-a-curl-command-with-xml--g61</guid>
      <description>&lt;p&gt;HTTP requests are rarely static in production. A header should only be sent when a feature flag is on, verbose logging should appear only in debug sessions, and an endpoint variant should be selected by environment. JQuickCurl's XML mode solves this with &lt;strong&gt;conditional rendering&lt;/strong&gt;: an &lt;code&gt;&amp;lt;if test="..."&amp;gt;&lt;/code&gt; element whose enclosed curl text is included only when the expression evaluates to true — evaluated per request, at runtime.&lt;/p&gt;

&lt;p&gt;This post shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; composes with the XML catalog from Post 7.&lt;/li&gt;
&lt;li&gt;Realistic conditions: headers, options, and body fragments.&lt;/li&gt;
&lt;li&gt;A complete runnable demo with a toggled debug mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Rule: Only What's True Gets Rendered
&lt;/h2&gt;

&lt;p&gt;Inside a &lt;code&gt;&amp;lt;curl&amp;gt;&lt;/code&gt; element you can wrap any curl fragment in &lt;code&gt;&amp;lt;if test="expression"&amp;gt;&lt;/code&gt;. If the expression holds, the fragment's text is spliced into the final command; otherwise it disappears as if never written.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"getUserByIdVariable"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"com.example.model.User"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    curl -X GET #{host} \
    &lt;span class="nt"&gt;&amp;lt;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"a == 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; -H "Content-Type: application/json" &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expression reads values from the request context — which includes the &lt;code&gt;@Param&lt;/code&gt;-annotated arguments you pass to the interface method (Post 7). Here, the extra header appears only when parameter &lt;code&gt;a&lt;/code&gt; equals &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Pattern 1: Toggle Debug Verbosity
&lt;/h2&gt;

&lt;p&gt;Wrap the curl logging flag so that production traffic is silent while a debug flag on the interface adds &lt;code&gt;-v&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;curls&lt;/span&gt; &lt;span class="na"&gt;namespace=&lt;/span&gt;&lt;span class="s"&gt;"com.example.search.SearchApi"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        curl -X GET 'https://api.example.com/search?q=#{keyword}&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;page=#{page}'
        &lt;span class="nt"&gt;&amp;lt;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"debug == true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; -v &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/curls&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;SearchApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"keyword"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"debug"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;search("http", 1, true)&lt;/code&gt; adds &lt;code&gt;-v&lt;/code&gt; to the command; calling it with &lt;code&gt;false&lt;/code&gt; drops it. The same request shape, one conditional branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Pattern 2: Conditional Header Injection
&lt;/h2&gt;

&lt;p&gt;Environment-sensitive headers are a perfect &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; target. Inject a tracing header only when a &lt;code&gt;traceId&lt;/code&gt; is actually present, and an API-key header only for the private environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    curl -X GET '#{host}/orders?status=#{status}'
    &lt;span class="nt"&gt;&amp;lt;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"traceId != null and traceId != ''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; -H "X-Trace-Id: #{traceId}" &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"env == 'prd'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; -H "X-Env: prd" -H "Authorization: Bearer #{token}" &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"host"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"traceId"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;traceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"env"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same method, but the wire request differs with runtime state — no &lt;code&gt;if/else&lt;/code&gt; in Java, no empty headers being sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Pattern 3: Choose the Payload
&lt;/h2&gt;

&lt;p&gt;Conditions can also select between body fragments. Sending a body only for methods that need one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"report"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    curl -X POST 'https://api.example.com/reports'
    -H 'Content-Type: application/json'
    -d '{"type":"#{kind}"}'
    &lt;span class="nt"&gt;&amp;lt;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"includeFilter == true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; ,"filter":{"min":#{min}} &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;When splicing JSON fragments, keep the result valid JSON after substitution. Several small &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; blocks are easier to reason about than one opaque mega-expression.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Runnable Demo
&lt;/h2&gt;

&lt;p&gt;Put the catalog below on the classpath as &lt;code&gt;search.xml&lt;/code&gt; and run the Java class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE curls PUBLIC "-//PAOHAIJIAO//DTD API CURL 1.0//EN"
        "classpath:paohaijiao/dtd/Jquick-curl.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;curls&lt;/span&gt; &lt;span class="na"&gt;namespace=&lt;/span&gt;&lt;span class="s"&gt;"com.example.search.SearchApi"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        curl -X GET 'https://httpbin.org/anything/search?q=#{q}'
        &lt;span class="nt"&gt;&amp;lt;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"verbose == true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; -v &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"withHeader == true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; -H "X-Demo: conditional" &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/curls&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.JQuickCurlXmlParseFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.factory.JQuickXmlFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.handler.JQuickParseHandler&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.param.Param&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;SearchApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"q"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"verbose"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                  &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"withHeader"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;withHeader&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConditionalDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickParseHandler&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlXmlParseFactory&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;SearchApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickXmlFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"search.xml"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;createApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SearchApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Verbose + header enabled&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Both disabled — the request on the wire is different&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the endpoint is &lt;code&gt;httpbin.org/anything&lt;/code&gt;, the response echoes the headers and args the server actually received — you can visually confirm that &lt;code&gt;X-Demo&lt;/code&gt; appears only in the first call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt;: The Full XML Control Set
&lt;/h2&gt;

&lt;p&gt;The bundled DTD defines more than &lt;code&gt;if&lt;/code&gt;. XML content may also use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;foreach collection="..." item="..."&amp;gt;&lt;/code&gt; — repeat content over a collection with &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, and &lt;code&gt;separator&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;choose&amp;gt; / &amp;lt;when test="..."&amp;gt; / &amp;lt;otherwise&amp;gt;&lt;/code&gt; — multi-branch selection, similar to a switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use them for the same goal: keeping the curl command truthful while letting runtime data shape it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;XML &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; turns a static curl catalog into a runtime decision graph. Headers, flags, and payload fragments are included only when their &lt;code&gt;test&lt;/code&gt; expression is true, evaluated fresh on every invocation. The Java interface stays declarative and the request stays readable — the conditional logic lives where the request is defined, not sprinkled through callers.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;. Post 9 covers the opposite end of HTTP payloads: binary and multipart file uploads through curl's &lt;code&gt;-F&lt;/code&gt; flag.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>api</category>
      <category>restapi</category>
      <category>http</category>
      <category>java</category>
    </item>
    <item>
      <title>XML Configuration Mode: Decoupling Your API Catalog from Business Code</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Sat, 12 Sep 2026 23:23:45 +0000</pubDate>
      <link>https://dev.to/paohaijiao/xml-configuration-mode-decoupling-your-api-catalog-from-business-code-56nm</link>
      <guid>https://dev.to/paohaijiao/xml-configuration-mode-decoupling-your-api-catalog-from-business-code-56nm</guid>
      <description>&lt;p&gt;Annotation mode keeps requests close to Java, but large integrations raise a fair question: &lt;em&gt;do I want a wall of curl strings inside my service interfaces?&lt;/em&gt; JQuickCurl's XML mode answers with a firm &lt;strong&gt;no&lt;/strong&gt; — you store every API definition in a separate XML file, and the business layer only sees plain Java interfaces with no annotations at all. This is the classic "declarative client" split: contract in configuration, behavior in code.&lt;/p&gt;

&lt;p&gt;In this post you'll learn to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author an XML API catalog with the official DTD.&lt;/li&gt;
&lt;li&gt;Bind it to a plain Java interface automatically.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;#{...}&lt;/code&gt; context variables in XML.&lt;/li&gt;
&lt;li&gt;Swap endpoint behavior without recompiling business code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Big Idea
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apis.xml   (curl commands + return types)        business code
   |                                                  |
   +-- parsed by JQuickCurlXmlParseFactory ----------&amp;gt; plain interface proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The XML file names each request and declares its return type. The factory binds those entries to interface methods &lt;strong&gt;by method name&lt;/strong&gt;. Change the curl in XML → the running app changes behavior. No Java edits, no rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Write the XML Catalog
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;src/main/resources/apis.xml&lt;/code&gt;. The &lt;code&gt;DOCTYPE&lt;/code&gt; references the bundled DTD so your IDE can validate structure as you type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE curls PUBLIC "-//PAOHAIJIAO//DTD API CURL 1.0//EN"
        "classpath:paohaijiao/dtd/Jquick-curl.dtd"&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;curls&lt;/span&gt; &lt;span class="na"&gt;namespace=&lt;/span&gt;&lt;span class="s"&gt;"com.example.weather.WeatherApi"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"current"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        curl -X GET 'https://api.open-meteo.com/v1/forecast?latitude=52.52&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;longitude=13.41&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;current_weather=true'
    &lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"currentAt"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        curl -X GET 'https://api.open-meteo.com/v1/forecast?latitude=#{lat}&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;longitude=#{lon}&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;current_weather=true'
    &lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sunrise"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        curl -X GET 'https://api.sunrise-sunset.org/json?lat=#{lat}&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;lng=#{lon}&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;formatted=0'
    &lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/curls&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules to remember while authoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;curls namespace="..."&amp;gt;&lt;/code&gt; identifies the interface the file maps to.&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;&amp;lt;curl&amp;gt;&lt;/code&gt; has &lt;code&gt;name&lt;/code&gt; (must equal the Java method name) and &lt;code&gt;returnClass&lt;/code&gt; (fully qualified).&lt;/li&gt;
&lt;li&gt;The text content is the curl command; &lt;code&gt;#{name}&lt;/code&gt; placeholders are resolved at runtime from context/method parameters.&lt;/li&gt;
&lt;li&gt;Optional &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;foreach&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;choose&amp;gt;&lt;/code&gt; children make content conditional (Post 8 covers &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; in depth).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Write a Pure Java Interface
&lt;/h2&gt;

&lt;p&gt;No annotations, no HTTP imports — just signatures matching the &lt;code&gt;name&lt;/code&gt; attributes. Parameters are bound with &lt;code&gt;@Param("...")&lt;/code&gt; (import from the xml module) so the XML can reference them via &lt;code&gt;#{...}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.param.Param&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;WeatherApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;currentAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lon"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;sunrise&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lon"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Build the Proxy from the XML Factory
&lt;/h2&gt;

&lt;p&gt;JQuickCurl ships its own parse handler; the generic XML-proxy factory comes from the companion library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.JQuickCurlXmlParseFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.factory.JQuickXmlFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.xml.handler.JQuickParseHandler&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;XmlDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Parser that understands JQuickCurl XML.&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickParseHandler&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlXmlParseFactory&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Factory bound to the classpath resource "apis.xml".&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickXmlFactory&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickXmlFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"apis.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. One proxy for the whole catalog.&lt;/span&gt;
        &lt;span class="nc"&gt;WeatherApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WeatherApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;52.52&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;13.41&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sunrise&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;52.52&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;13.41&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output: JSON from Open-Meteo and the sunrise/sunset API. The calls above run without authentication and prove the whole round trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Second Catalog Without Touching Java
&lt;/h2&gt;

&lt;p&gt;Want to point &lt;code&gt;current&lt;/code&gt; at a different provider, add a header, or insert retry semantics? Edit only the XML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;curl&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"current"&lt;/span&gt; &lt;span class="na"&gt;returnClass=&lt;/span&gt;&lt;span class="s"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    curl -X GET 'https://api.open-meteo.com/v1/forecast?latitude=52.52&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;longitude=13.41&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;current_weather=true' \
    -H 'Accept: application/json'
&lt;span class="nt"&gt;&amp;lt;/curl&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy, the interface, and all callers stay unchanged. That is the entire value proposition: &lt;strong&gt;API drift is fixed in one configuration file, not across dozens of call sites.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  XML Mode vs Annotation Mode — When to Choose Which
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Annotation mode (&lt;code&gt;@JCurlCommand&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;XML mode (&lt;code&gt;apis.xml&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request location&lt;/td&gt;
&lt;td&gt;Next to Java code&lt;/td&gt;
&lt;td&gt;Centralized config file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change without rebuild&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDE help&lt;/td&gt;
&lt;td&gt;Java syntax&lt;/td&gt;
&lt;td&gt;XML + DTD validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variables&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;${name}&lt;/code&gt; from &lt;code&gt;JQuickCurlReq&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#{name}&lt;/code&gt; from context / &lt;code&gt;@Param&lt;/code&gt; args&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Few endpoints, team owns the code&lt;/td&gt;
&lt;td&gt;Large third-party API catalogs, shared config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;XML configuration mode decouples &lt;em&gt;what the HTTP call is&lt;/em&gt; from &lt;em&gt;who calls it&lt;/em&gt;. You get one searchable, validated, DTD-checked catalog of every integration your service owns, while Java interfaces stay free of annotation noise. This pattern pays off exactly when your API surface is wide and changes often.&lt;/p&gt;

&lt;p&gt;Source project: &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;. Post 8 pushes XML mode further with &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; conditions that render parts of a request only when runtime state says so.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>api</category>
      <category>restapi</category>
      <category>java</category>
      <category>http</category>
    </item>
    <item>
      <title>REST in Annotation Mode: JSON GET, POST, PUT, and DELETE with @JCurlCommand</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Sat, 12 Sep 2026 02:11:10 +0000</pubDate>
      <link>https://dev.to/paohaijiao/rest-in-annotation-mode-json-get-post-put-and-delete-with-jcurlcommand-5hkm</link>
      <guid>https://dev.to/paohaijiao/rest-in-annotation-mode-json-get-post-put-and-delete-with-jcurlcommand-5hkm</guid>
      <description>&lt;p&gt;After the Hello World in Post 1, it's time for the classic backend bread-and-butter: a full JSON CRUD cycle. In annotation mode each HTTP verb is just a different curl command sitting on its own interface method. We'll build a small user-management demo against the free &lt;a href="https://reqres.in/" rel="noopener noreferrer"&gt;ReqRes&lt;/a&gt; REST API and show four return-type styles: &lt;code&gt;String&lt;/code&gt;, a domain object, a collection, and &lt;code&gt;Void&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The curl Commands Behind Each Verb
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Read one resource (returns a JSON object)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET https://reqres.in/api/users/2

&lt;span class="c"&gt;# Create a resource (sends JSON, returns the created object with a new id)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://reqres.in/api/users &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"Ada","job":"Engineer"}'&lt;/span&gt;

&lt;span class="c"&gt;# Replace / update a resource&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT https://reqres.in/api/users/2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"Ada Lovelace","job":"Analyst"}'&lt;/span&gt;

&lt;span class="c"&gt;# Delete a resource (returns an empty body, HTTP 204)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE https://reqres.in/api/users/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of these strings is a valid &lt;code&gt;@JCurlCommand&lt;/code&gt; value — no request-builder API involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CRUD Interface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UserCrudApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://reqres.in/api/users/${id}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://reqres.in/api/users' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-d '{\"name\":\"${name}\",\"job\":\"${job}\"}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X PUT 'https://reqres.in/api/users/${id}' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-d '{\"name\":\"${name}\",\"job\":\"${job}\"}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X DELETE 'https://reqres.in/api/users/${id}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Void&lt;/span&gt; &lt;span class="nf"&gt;deleteUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Method name, HTTP verb, and URL are decoupled — rename methods freely.&lt;/li&gt;
&lt;li&gt;The return type drives conversion: here &lt;code&gt;String&lt;/code&gt; and &lt;code&gt;Void&lt;/code&gt; (&lt;code&gt;Void&lt;/code&gt; discards the body for DELETE).&lt;/li&gt;
&lt;li&gt;JSON bodies use the &lt;code&gt;${...}&lt;/code&gt; placeholders from Post 5, so &lt;code&gt;createUser&lt;/code&gt; and &lt;code&gt;updateUser&lt;/code&gt; share one command shape each.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Driving the Full Cycle
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CrudDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;UserCrudApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserCrudApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// CREATE&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Ada"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"job"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Engineer"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"created: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// JSON incl. new id&lt;/span&gt;

        &lt;span class="c1"&gt;// READ (needs an id — parse it from the create response in real code)&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"read: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// UPDATE&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Ada Lovelace"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"job"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Analyst"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"updated: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// DELETE&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deleteUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"deleted OK"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected behavior: ReqRes is a mock API, so responses are deterministic — &lt;code&gt;POST /api/users&lt;/code&gt; answers with the payload plus an &lt;code&gt;id&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; returns HTTP 204.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond String: Typed Return Values
&lt;/h2&gt;

&lt;p&gt;Declaring the method return type is all it takes to skip manual JSON parsing. The response converter maps the JSON body onto your class — mirror the response keys with your fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserInfo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters omitted for brevity&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TypedCrudApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://reqres.in/api/users/${id}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;UserInfo&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://reqres.in/api/users?page=${page}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;listUsers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other supported return types worth remembering:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Return type&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raw response body as text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;byte[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raw body bytes (great for binary endpoints)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your POJO&lt;/td&gt;
&lt;td&gt;Body JSON bound to the object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.util.List&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON array bound to a list of objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute and discard the response body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;JResult&lt;/code&gt; / &lt;code&gt;JQuickCurlResponseBody&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Keep the raw response with extra accessors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE
&lt;/h2&gt;

&lt;p&gt;JQuickCurl's parser and executor cover eight methods today: &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt;, and &lt;code&gt;TRACE&lt;/code&gt;. PATCH follows the same pattern as PUT with a smaller payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X PATCH 'https://reqres.in/api/users/${id}' "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-d '{\"job\":\"${job}\"}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;patchUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HEAD and OPTIONS usually return no meaningful body — return &lt;code&gt;Void&lt;/code&gt; — and TRACE is a bodyless diagnostic verb on the executor side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CRUD in annotation mode is literally your tested curl commands with the volatile values promoted to &lt;code&gt;${...}&lt;/code&gt; placeholders. Method naming, return-type conversion (&lt;code&gt;String&lt;/code&gt;, POJO, &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;Void&lt;/code&gt;, &lt;code&gt;byte[]&lt;/code&gt;, …), and header handling are all declarative. This is the pattern the rest of the series builds on — XML configuration, conditionals, uploads, and downloads are just more curl commands wearing different clothes.&lt;/p&gt;

&lt;p&gt;Everything here runs against the open-source &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;. Post 7 shows the same idea, but with API definitions lifted out of Java entirely and into XML.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>rest</category>
      <category>restapi</category>
      <category>java</category>
    </item>
    <item>
      <title>Deep Dive into @JCurlCommand: The Annotation and Its Variable Substitution Grammar</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Thu, 10 Sep 2026 22:36:17 +0000</pubDate>
      <link>https://dev.to/paohaijiao/deep-dive-into-jcurlcommand-the-annotation-and-its-variable-substitution-grammar-59e8</link>
      <guid>https://dev.to/paohaijiao/deep-dive-into-jcurlcommand-the-annotation-and-its-variable-substitution-grammar-59e8</guid>
      <description>&lt;p&gt;&lt;code&gt;@JCurlCommand&lt;/code&gt; is the heart of JQuickCurl's annotation mode. One annotation turns a static curl string into a dynamic, typed HTTP method. This post covers the annotation's full surface and — more importantly — the &lt;code&gt;${...}&lt;/code&gt; variable grammar that makes one command reusable across thousands of runtime states.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Annotation, Decoded
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@JCurlCommand&lt;/code&gt; targets methods and is retained at runtime so the proxy layer can read it on every invocation:&lt;/p&gt;

&lt;p&gt;In practice you will use &lt;code&gt;value()&lt;/code&gt; on every method; the other attributes give you a declarative place to document the response contract for a command. The empty constructor proxy flow rejects methods annotated with &lt;code&gt;execute = false&lt;/code&gt;, so treat &lt;code&gt;execute&lt;/code&gt; as "this annotation is a real executable command".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;HealthApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"curl -X GET https://httpbin.org/status/200"&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The &lt;code&gt;${name}&lt;/code&gt; Variable Grammar
&lt;/h2&gt;

&lt;p&gt;JQuickCurl embeds variables &lt;em&gt;inside&lt;/em&gt; the curl string using &lt;code&gt;${name}&lt;/code&gt;. At execution time the proxy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collects the keys you &lt;code&gt;put&lt;/code&gt; into &lt;code&gt;JQuickCurlReq&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copies them into the request context (&lt;code&gt;JContext&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Replaces every &lt;code&gt;${name}&lt;/code&gt; occurrence with the corresponding runtime value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because substitution happens &lt;strong&gt;before&lt;/strong&gt; the HTTP call, you can place placeholders anywhere syntax allows — URL path, query string, header value, auth pair, or request body.&lt;/p&gt;

&lt;h3&gt;
  
  
  URLs and Paths
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;GithubLikeApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/repos/${owner}/${repo}/issues/${issueId}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getIssue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"owner"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dromara"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"repo"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"jquick-curl"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"issueId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIssue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// GET /repos/dromara/jquick-curl/issues/42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query Strings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/search?q=${query}&amp;amp;page=${page}&amp;amp;size=${size}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http client"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"size"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Headers and Authentication Pairs
&lt;/h3&gt;

&lt;p&gt;Variables are not limited to URLs — credentials belong there too, so secrets never sit in source code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AuthApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/me' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Authorization: Bearer ${token}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -u '${user}:${password}' https://api.example.com/account"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;account&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API_TOKEN"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// never hard-code secrets&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"demo"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API_PASSWORD"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Request Bodies
&lt;/h3&gt;

&lt;p&gt;When the JSON body must change per call, build it in Java and substitute it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://api.example.com/orders' "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-d '${payload}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{\"customer\":\"Ada\",\"amount\":99.5,\"items\":[1,2,3]}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payload"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Careful: the body is inserted verbatim, so the JSON must already be valid (quotes, braces, commas) before substitution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Method Parameters as Variables (Alternative Style)
&lt;/h2&gt;

&lt;p&gt;Besides the &lt;code&gt;JQuickCurlReq&lt;/code&gt; map, JQuickCurl can substitute &lt;code&gt;${name}&lt;/code&gt; from &lt;strong&gt;method parameter names&lt;/strong&gt; when a method declares extra parameters — the placeholder must equal the Java parameter's name. This gives you compiler-visible signatures for static call sites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/orders/${orderId}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For dynamic multi-value cases the map style is far more flexible, which is why the rest of this series standardizes on &lt;code&gt;JQuickCurlReq&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quoting Rules and Escaping Cheat-Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you write&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'https://…/${id}'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single-quoted value with a variable — preferred style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"Authorization: Bearer ${token}"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Double-quoted value; you must escape as &lt;code&gt;\"&lt;/code&gt; inside a Java annotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-d '{"a":1}'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Body with double quotes → escape them in Java: &lt;code&gt;"{\"a\":1}"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;${key}&lt;/code&gt; with a missing key&lt;/td&gt;
&lt;td&gt;Left unresolved — the request context simply has no such entry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a response surprises you, first check whether the placeholder key actually made it into &lt;code&gt;JQuickCurlReq&lt;/code&gt; before the call (see Post 17's troubleshooting checklist).&lt;/p&gt;

&lt;h2&gt;
  
  
  Runnable Demo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;EchoApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://httpbin.org/anything/${channel}?trace=${traceId}' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'X-Scope: ${scope}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VariableDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;EchoApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EchoApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"channel"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mobile"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"traceId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"trc-9911"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"scope"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"readonly"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="c1"&gt;// httpbin echoes url, headers, args — showing every variable resolved.&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@JCurlCommand&lt;/code&gt; provides the executable surface (the curl string) and a small set of contract attributes, while &lt;code&gt;${name}&lt;/code&gt; placeholders supply the dynamism. Variables work anywhere in the command — path, query, headers, auth, and bodies — and their values come from the same &lt;code&gt;JQuickCurlReq&lt;/code&gt; map that every method in this series already uses. Combined with the proxy pattern, one annotated command now serves an unlimited number of runtime requests.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;. Post 6 applies this knowledge to the full REST alphabet — JSON GET, POST, PUT, and DELETE in annotation mode.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>api</category>
      <category>restapi</category>
      <category>httpclient</category>
      <category>java</category>
    </item>
    <item>
      <title>Stop Translating: Reuse Postman and Browser-Copied curl Fragments Directly in Java</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Wed, 09 Sep 2026 22:20:17 +0000</pubDate>
      <link>https://dev.to/paohaijiao/stop-translating-reuse-postman-and-browser-copied-curl-fragments-directly-in-java-1hdl</link>
      <guid>https://dev.to/paohaijiao/stop-translating-reuse-postman-and-browser-copied-curl-fragments-directly-in-java-1hdl</guid>
      <description>&lt;p&gt;"How did you test that endpoint?" — "I copied the curl from Postman." That sentence is why JQuickCurl exists. When a request works in Postman or the browser DevTools, the raw curl fragment is already the &lt;em&gt;ground truth&lt;/em&gt;. In this post you'll learn a repeatable recipe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export curl from Postman or copy it from Chrome DevTools.&lt;/li&gt;
&lt;li&gt;Sanitize it (remove volatile headers and secrets).&lt;/li&gt;
&lt;li&gt;Paste it into &lt;code&gt;@JCurlCommand&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Turn the moving parts into &lt;code&gt;${...}&lt;/code&gt; variables.&lt;/li&gt;
&lt;li&gt;Run the exact same request from Java.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Get the curl Snippet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From Postman:&lt;/strong&gt; open your saved request → the &lt;code&gt;&amp;lt;/&amp;gt;&lt;/code&gt; Code button in the top-right → choose &lt;strong&gt;cURL&lt;/strong&gt; → copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From Chrome DevTools:&lt;/strong&gt; open the Network tab → right-click the request you care about → &lt;strong&gt;Copy → Copy as cURL (bash)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A typical browser copy looks like this (notice the noise):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'https://api.example.com/orders/1024'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json, text/plain, */*'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept-language: en-US,en;q=0.9'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'cookie: _ga=GA1.2.abc; session=7f3a9c1e...'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'sec-ch-ua: "Chromium";v="120", "Google Chrome";v="120"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'sec-fetch-site: same-origin'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'user-agent: Mozilla/5.0 ...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Sanitize Before You Paste
&lt;/h2&gt;

&lt;p&gt;Rule number one of reusing curl in server code: &lt;strong&gt;the browser context is not your server context&lt;/strong&gt;. Remove or externalize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cookie&lt;/code&gt; / &lt;code&gt;authorization&lt;/code&gt; values (inject them at runtime instead — see Post 12 on interceptors).&lt;/li&gt;
&lt;li&gt;Browser fingerprint headers (&lt;code&gt;sec-ch-ua&lt;/code&gt;, &lt;code&gt;sec-fetch-*&lt;/code&gt;, &lt;code&gt;user-agent&lt;/code&gt;) unless the API genuinely validates them.&lt;/li&gt;
&lt;li&gt;Your &lt;code&gt;_ga&lt;/code&gt; tracking cookies — you do not want analytics cookies in server logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the semantic core: method, URL, &lt;code&gt;Content-Type&lt;/code&gt;, real payload, and any header the API contract actually requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Paste into an Annotation — and Mind the Escaping
&lt;/h2&gt;

&lt;p&gt;Collapse the multi-line shell command onto one line and put it inside &lt;code&gt;@JCurlCommand&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/orders/1024' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'accept: application/json' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'X-Client: jquick-curl'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java-string escaping rules are the only friction you'll meet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postman/browser use double quotes in headers (&lt;code&gt;-H "Accept: ..."&lt;/code&gt;); inside an annotation they must be escaped or switched to single quotes. I recommend single quotes — they read cleanly and match shell-style curl.&lt;/li&gt;
&lt;li&gt;If your JSON body itself contains single quotes, escape them with &lt;code&gt;\'&lt;/code&gt; inside the single-quoted value, or keep double quotes and escape them as &lt;code&gt;\"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: concatenating adjacent string literals (&lt;code&gt;"..." + "..."&lt;/code&gt;) keeps long commands readable and still satisfies Java's requirement that annotation values be compile-time constants.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Make the Moving Parts Dynamic
&lt;/h2&gt;

&lt;p&gt;A hard-coded order ID dies on the first real request. Replace volatile values with &lt;code&gt;${name}&lt;/code&gt; placeholders and supply them through &lt;code&gt;JQuickCurlReq&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET 'https://api.example.com/orders/${orderId}' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'accept: application/json'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orderId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;JQuickCurlReq&lt;/code&gt; is literally a &lt;code&gt;HashMap&amp;lt;String, Object&amp;gt;&lt;/code&gt;, so &lt;code&gt;put&lt;/code&gt;/&lt;code&gt;get&lt;/code&gt; behave exactly as you'd expect. Whatever key you place there can be referenced as &lt;code&gt;${key}&lt;/code&gt; anywhere in the command — URL, header value, &lt;code&gt;-u&lt;/code&gt; credentials, &lt;code&gt;-d&lt;/code&gt; body.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Full "Pasted from Postman" Demo
&lt;/h2&gt;

&lt;p&gt;Here's a complete runnable example using the public httpbin echo service. Copy this curl from your own Postman and you can verify the flow end-to-end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PostmanApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// The exact command you exported, after removing volatile headers.&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST 'https://httpbin.org/post' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-d '{\"orderId\":${orderId},\"customer\":\"${customer}\"}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;postOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Demo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;PostmanApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PostmanApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orderId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;555&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customer"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hashnode reader"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;postOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="c1"&gt;// httpbin echoes back the "data" field containing your substituted JSON.&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What If the Pasted Command Fails to Parse?
&lt;/h2&gt;

&lt;p&gt;JQuickCurl's parser targets the curl options its test suite covers. Before debugging your own code, double-check that the exported snippet only uses these option families: &lt;code&gt;-X/--request&lt;/code&gt;, &lt;code&gt;-H/--header&lt;/code&gt;, &lt;code&gt;-d/--data(-ascii|-binary|-raw|-urlencode)&lt;/code&gt;, &lt;code&gt;-u/--user&lt;/code&gt;, &lt;code&gt;-L/--location/--max-redirs&lt;/code&gt;, &lt;code&gt;-F/--form&lt;/code&gt;, &lt;code&gt;-o/--output&lt;/code&gt;, &lt;code&gt;-x/--proxy/--socks5-hostname&lt;/code&gt;, plus &lt;code&gt;--http2&lt;/code&gt;, &lt;code&gt;-k/--insecure&lt;/code&gt;, &lt;code&gt;-v/--verbose&lt;/code&gt;, &lt;code&gt;-s/--silent&lt;/code&gt;. Exotic flags (&lt;code&gt;--compressed&lt;/code&gt;, &lt;code&gt;--resolve&lt;/code&gt;, &lt;code&gt;--path-as-is&lt;/code&gt;, …) are not part of the supported set — strip them from the fragment you paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The workflow that used to take "translate request → write builder code → pray" now takes seconds: &lt;strong&gt;copy curl → sanitize → paste → parameterize&lt;/strong&gt;. JQuickCurl closes the gap between the tool where you debug HTTP (Postman/browser) and the code where you ship it. All examples in this series run against &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next up, in Post 5, we take a deep dive into the &lt;code&gt;@JCurlCommand&lt;/code&gt; annotation and its variable-substitution grammar — the feature that turns static curl strings into dynamic production requests.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>api</category>
      <category>restapi</category>
      <category>rest</category>
      <category>httpclient</category>
    </item>
    <item>
      <title>JQuickCurl vs OkHttp vs RestTemplate vs OpenFeign: A Honest Comparison for Backend Engineers</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Tue, 08 Sep 2026 23:26:21 +0000</pubDate>
      <link>https://dev.to/paohaijiao/jquickcurl-vs-okhttp-vs-resttemplate-vs-openfeign-a-honest-comparison-for-backend-engineers-320i</link>
      <guid>https://dev.to/paohaijiao/jquickcurl-vs-okhttp-vs-resttemplate-vs-openfeign-a-honest-comparison-for-backend-engineers-320i</guid>
      <description>&lt;p&gt;Choosing an HTTP client is a "boring but expensive" decision: it touches every downstream integration you own. OkHttp, Spring's RestTemplate, OpenFeign, and now curl-driven JQuickCurl all &lt;em&gt;work&lt;/em&gt; — the real question is &lt;strong&gt;who should write and maintain your request definitions&lt;/strong&gt;. This post compares the four on the dimensions that actually matter in a backend codebase, then gives you a decision framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contenders in One Line Each
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;Request definition style&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OkHttp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hand-written &lt;code&gt;Request.Builder&lt;/code&gt; code&lt;/td&gt;
&lt;td&gt;Low-level control, streaming, custom protocols, performance-critical paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RestTemplate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hand-written URIs + &lt;code&gt;HttpEntity&lt;/code&gt;/&lt;code&gt;HttpHeaders&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Classic Spring MVC codebases (deprecated in spirit by Spring in favor of WebClient)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenFeign&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Annotated Java interfaces (&lt;code&gt;@GetMapping&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;Spring Cloud microservices with server-side (or declarative) contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JQuickCurl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Raw curl strings parsed by ANTLR4, plus variables &amp;amp; XML&lt;/td&gt;
&lt;td&gt;Teams that live in Postman/browser curl and want zero hand-written request construction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What the Code Looks Like Side by Side
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;OkHttp — everything explicit, everything manual:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;HttpUrl&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://jsonplaceholder.typicode.com/posts"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addQueryParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"userId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;RequestBody&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RequestBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"{\"title\":\"ok\",\"body\":\"bar\",\"userId\":1}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json; charset=utf-8"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accept"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;newCall&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;RestTemplate — annotations gone, strings everywhere:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;RestTemplate&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RestTemplate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;HttpHeaders&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpHeaders&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;HttpEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"{\"title\":\"ok\",\"userId\":1}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://jsonplaceholder.typicode.com/posts?userId={id}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBody&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;OpenFeign — contract-driven, but only if the contract exists:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@FeignClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://jsonplaceholder.typicode.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PostClient&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/posts"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JQuickCurl — the tested curl snippet is the code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PostApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST https://jsonplaceholder.typicode.com/posts "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-d '{\"title\":\"ok\",\"body\":\"bar\",\"userId\":1}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The differences become obvious: OkHttp/RestTemplate force you to &lt;em&gt;re-describe&lt;/em&gt; the request in builder API; OpenFeign introduces a &lt;em&gt;parallel annotation vocabulary&lt;/em&gt;; JQuickCurl reuses the &lt;em&gt;universal curl vocabulary&lt;/em&gt; you already test with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison Table: What Actually Differs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;OkHttp&lt;/th&gt;
&lt;th&gt;RestTemplate&lt;/th&gt;
&lt;th&gt;OpenFeign&lt;/th&gt;
&lt;th&gt;JQuickCurl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reuse Postman/browser curl&lt;/td&gt;
&lt;td&gt;Manual rewrite&lt;/td&gt;
&lt;td&gt;Manual rewrite&lt;/td&gt;
&lt;td&gt;Manual rewrite&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Paste directly&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic requests (headers/body per call)&lt;/td&gt;
&lt;td&gt;Code branches&lt;/td&gt;
&lt;td&gt;Code branches&lt;/td&gt;
&lt;td&gt;SpEL / interceptors&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Variables &lt;code&gt;${...}&lt;/code&gt; + XML &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-team sharing of request format&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Contract stubs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Same curl string&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proxy / dynamic invocation&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (&lt;code&gt;createProxy&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeouts/retry/pool out of the box&lt;/td&gt;
&lt;td&gt;Builder&lt;/td&gt;
&lt;td&gt;Some&lt;/td&gt;
&lt;td&gt;Via config&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Global &lt;code&gt;JQuickCurlConfig&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multipart upload / download&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;-F&lt;/code&gt; / &lt;code&gt;--output&lt;/code&gt; in curl&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Cloud integration&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Native template&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bean wiring (manual, simple)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency weight&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Spring Web&lt;/td&gt;
&lt;td&gt;Spring Cloud stack&lt;/td&gt;
&lt;td&gt;OkHttp + ANTLR runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve for new devs&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low–Medium&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Low if they know curl&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to Pick JQuickCurl (Honest Scenarios)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose JQuickCurl when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You or your QA team already maintain curl collections in Postman and want them as executable Java code.&lt;/li&gt;
&lt;li&gt;Third-party OpenAPI docs ship curl samples and you must integrate many endpoints quickly (see Post 18).&lt;/li&gt;
&lt;li&gt;You need dynamic requests that depend on runtime state (auth tokens, environment hosts, feature flags) without writing factory methods per endpoint.&lt;/li&gt;
&lt;li&gt;You want API definitions externalized in XML, decoupled from business code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Still choose the alternatives when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are deep inside the Spring Cloud ecosystem and &lt;em&gt;already own&lt;/em&gt; Feign clients — the load-balancer/service-discovery integration is a solved problem there.&lt;/li&gt;
&lt;li&gt;You need WebFlux/reactive non-blocking I/O — JQuickCurl's model is synchronous OkHttp calls (though OkHttp itself is async-capable under the hood).&lt;/li&gt;
&lt;li&gt;You only have a handful of endpoints and the team knows one client cold — introducing any new abstraction has a cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Do your requests already exist as curl commands? → strong signal for JQuickCurl.&lt;/li&gt;
&lt;li&gt;Do you run a microservice mesh with Feign today? → stay on Feign unless curl reuse is a bigger pain.&lt;/li&gt;
&lt;li&gt;Do you need reactive streaming? → OkHttp/WebClient territory.&lt;/li&gt;
&lt;li&gt;Do you want one tool for the whole team to read requests? → JQuickCurl wins on readability.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;None of these tools is universally "best". OkHttp is the raw engine, RestTemplate the legacy workhorse, OpenFeign the Spring-Cloud native, and JQuickCurl the only one that treats &lt;strong&gt;curl as a first-class, shareable request language&lt;/strong&gt;. JQuickCurl is not a replacement for a full service-discovery stack — it's an alternative to hand-writing request builders, and in curl-heavy, integration-heavy codebases it removes a surprising amount of boilerplate. Source: &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Post 4 we'll do something every backend dev needs: reuse curl snippets exported straight from Postman and the browser, without translating them by hand.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>java</category>
      <category>restapi</category>
      <category>rest</category>
      <category>api</category>
    </item>
    <item>
      <title>JQuickCurl Meets Spring Boot: Turn Raw curl into Injectable HTTP Beans in 10 Minutes</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Tue, 08 Sep 2026 11:57:46 +0000</pubDate>
      <link>https://dev.to/paohaijiao/jquickcurl-meets-spring-boot-turn-raw-curl-into-injectable-http-beans-in-10-minutes-4f76</link>
      <guid>https://dev.to/paohaijiao/jquickcurl-meets-spring-boot-turn-raw-curl-into-injectable-http-beans-in-10-minutes-4f76</guid>
      <description>&lt;p&gt;Spring Boot developers reach for &lt;code&gt;RestTemplate&lt;/code&gt; or &lt;code&gt;WebClient&lt;/code&gt; out of habit, but they're both still &lt;em&gt;imperative builders&lt;/em&gt;: you hand-code the URL, the headers, and the body every single time. JQuickCurl brings a different workflow into your Spring Boot project — &lt;strong&gt;you define HTTP calls as curl commands on an interface, and Spring treats that interface's proxy like any other bean&lt;/strong&gt; you can &lt;code&gt;@Autowired&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This tutorial walks through a real Spring Boot setup step by step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding the dependency.&lt;/li&gt;
&lt;li&gt;Declaring a curl-powered service interface.&lt;/li&gt;
&lt;li&gt;Exposing the proxy as a Spring bean.&lt;/li&gt;
&lt;li&gt;Loading global HTTP settings from a properties file.&lt;/li&gt;
&lt;li&gt;Calling everything from a &lt;code&gt;@RestController&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: JQuickCurl ships as a plain library (no Spring Boot starter yet). Wiring it as a &lt;code&gt;@Configuration&lt;/code&gt; bean is a few lines of code and keeps full control in your hands.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 1: Dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;parent&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-parent&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.2.5&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;relativePath/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-web&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.paohaijiao&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jquick-curl&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.5.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Declare a curl-Driven Service Interface
&lt;/h2&gt;

&lt;p&gt;We call the public JSONPlaceholder API to fetch a "todo" item. Note the signature convention: the method receives a &lt;code&gt;JQuickCurlReq&lt;/code&gt;, and its return type is a domain object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TodoApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET https://jsonplaceholder.typicode.com/todos/1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;TodoBean&lt;/span&gt; &lt;span class="nf"&gt;getTodo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X POST https://jsonplaceholder.typicode.com/todos "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-H 'Content-Type: application/json' "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-d '{\"title\":\"learn jquick-curl\",\"completed\":false}'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;TodoBean&lt;/span&gt; &lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of Java-syntax tricks used above that will save you headaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Concatenated string literals are compile-time constants, so they're perfectly valid inside annotations — long curl commands stay readable.&lt;/li&gt;
&lt;li&gt;Inner double quotes inside the JSON body must be escaped (&lt;code&gt;\"&lt;/code&gt;), exactly as in any Java string.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The domain bean is a plain POJO (the response converter maps JSON onto its fields):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoBean&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters omitted for brevity&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Register the Proxy as a Spring Bean
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;JCurlInvoker.createProxy()&lt;/code&gt; returns a JDK proxy of your interface. We turn that into a singleton bean, so every controller or service can &lt;code&gt;@Autowired TodoApi&lt;/code&gt; and the proxy — and its OkHttp transport — is created only once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.config.JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpApiConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Optional: apply global HTTP settings (timeouts, pool, retry) at startup.&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HttpApiConfig&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadFromClasspathResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"quick-curl.properties"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;TodoApi&lt;/span&gt; &lt;span class="nf"&gt;todoApi&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TodoApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;JQuickCurlConfig&lt;/code&gt; is a process-wide singleton; the fluent builders below are also available if you prefer code over properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JQuickCurlConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxRetryCount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;followRedirects&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Properties-Driven Tuning
&lt;/h2&gt;

&lt;p&gt;Place &lt;code&gt;src/main/resources/quick-curl.properties&lt;/code&gt; on the classpath and load it with &lt;code&gt;loadFromClasspathResource(...)&lt;/code&gt; (keys shown here are the ones this API reads today):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;quick.curl.connect.timeout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3000&lt;/span&gt;
&lt;span class="py"&gt;quick.curl.read.timeout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10000&lt;/span&gt;
&lt;span class="py"&gt;quick.curl.write.timeout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10000&lt;/span&gt;
&lt;span class="py"&gt;quick.curl.pool.max.idle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20&lt;/span&gt;
&lt;span class="py"&gt;quick.curl.pool.keep.alive&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;300000&lt;/span&gt;
&lt;span class="py"&gt;quick.curl.max.retry.count&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your non-functional requirements (timeouts, connection pool, retry budget) live in configuration while your request definitions live in code — no more hunting for magic numbers inside service classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Consume the Bean from a Controller
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.PostMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TodoApi&lt;/span&gt; &lt;span class="n"&gt;todoApi&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TodoController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TodoApi&lt;/span&gt; &lt;span class="n"&gt;todoApi&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;todoApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;todoApi&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/todos/1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;TodoBean&lt;/span&gt; &lt;span class="nf"&gt;getTodo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;todoApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTodo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/todos"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;TodoBean&lt;/span&gt; &lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;todoApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createTodo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the application and test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn spring-boot:run
curl http://localhost:8080/api/todos/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"delectus aut autem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller stays clean: no &lt;code&gt;RestTemplate&lt;/code&gt;, no URL constants, no header maps — the HTTP knowledge is encapsulated in &lt;code&gt;@JCurlCommand&lt;/code&gt; annotations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Integrating JQuickCurl into Spring Boot only requires three small pieces: a curl-annotated interface, one &lt;code&gt;@Configuration&lt;/code&gt; class that exports its proxy as a bean, and an optional properties load for global tuning. From there, &lt;code&gt;@Autowired&lt;/code&gt; works exactly as it does with any Spring-managed HTTP abstraction.&lt;/p&gt;

&lt;p&gt;For a complete series context, all examples build on the &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt; project (Apache-2.0). In Post 3 we compare JQuickCurl against OkHttp, RestTemplate, and OpenFeign so you can decide, with evidence, when it deserves a place in your architecture.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>tutorial</category>
      <category>java</category>
      <category>api</category>
      <category>restapi</category>
    </item>
    <item>
      <title>Say Goodbye to Hand-Written HTTP Code</title>
      <dc:creator>paohaijiao</dc:creator>
      <pubDate>Mon, 07 Sep 2026 13:41:12 +0000</pubDate>
      <link>https://dev.to/paohaijiao/say-goodbye-to-hand-written-http-code-2145</link>
      <guid>https://dev.to/paohaijiao/say-goodbye-to-hand-written-http-code-2145</guid>
      <description>&lt;h1&gt;
  
  
  Say Goodbye to Hand-Written HTTP Code: Run Your curl Command Directly in Java with JQuickCurl
&lt;/h1&gt;

&lt;p&gt;Every backend engineer knows the drill: you spend ten minutes in Postman or the browser DevTools crafting the &lt;em&gt;perfect&lt;/em&gt; request, click "Copy as cURL", and then… stare at the screen. You have to manually translate headers, query strings, JSON bodies, and timeouts into &lt;code&gt;OkHttp&lt;/code&gt; builders, &lt;code&gt;RestTemplate&lt;/code&gt; exchanges, or &lt;code&gt;HttpClient&lt;/code&gt; request objects. Translation bugs sneak in, the code balloons, and nobody wants to maintain it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;JQuickCurl&lt;/a&gt; (a &lt;a href="https://dromara.org/" rel="noopener noreferrer"&gt;Dromara&lt;/a&gt; open-source project) takes the opposite approach: &lt;strong&gt;the curl command itself becomes your request definition&lt;/strong&gt;. You paste curl syntax into a Java annotation, declare an interface, and call it like a local method. The library parses your curl command with ANTLR4, executes it over the battle-tested OkHttp transport layer, and converts the response into whatever Java type you ask for.&lt;/p&gt;

&lt;p&gt;In this first post of the series you will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn what JQuickCurl is and when it shines.&lt;/li&gt;
&lt;li&gt;Add the dependency with Maven.&lt;/li&gt;
&lt;li&gt;Write a complete Hello-World example.&lt;/li&gt;
&lt;li&gt;Understand what happens behind the scenes (spoiler: no system curl binary is ever spawned).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Exactly Is JQuickCurl?
&lt;/h2&gt;

&lt;p&gt;JQuickCurl is a &lt;strong&gt;curl-command-driven HTTP client framework for Java&lt;/strong&gt;, licensed under &lt;strong&gt;Apache-2.0&lt;/strong&gt;. Its core idea: reuse the same curl snippets your frontend, QA, and API docs already share.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;curl syntax parsing&lt;/td&gt;
&lt;td&gt;ANTLR4 (in-process, no &lt;code&gt;ProcessBuilder&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP transport&lt;/td&gt;
&lt;td&gt;OkHttp 4.x (connection pool, HTTP/2, interceptors)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request definition&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@JCurlCommand&lt;/code&gt; annotation and XML files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic behavior&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;${...}&lt;/code&gt; variable substitution, XML &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra features&lt;/td&gt;
&lt;td&gt;file upload/download, batch execution, retry, timeouts, interceptors, dynamic proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because parsing happens &lt;strong&gt;inside the JVM&lt;/strong&gt;, JQuickCurl never calls the system &lt;code&gt;curl&lt;/code&gt; binary. That means the same Java code works on Windows, Linux, and macOS, needs no external installation, and keeps full programmatic control over the request lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Add the Maven Dependency
&lt;/h2&gt;

&lt;p&gt;Add this to your &lt;code&gt;pom.xml&lt;/code&gt; (check &lt;a href="https://central.sonatype.com/artifact/io.github.paohaijiao/jquick-curl" rel="noopener noreferrer"&gt;Maven Central&lt;/a&gt; for the newest release; the examples here use &lt;code&gt;2.5.0&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.paohaijiao&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jquick-curl&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.5.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Declare Your First curl-Powered API
&lt;/h2&gt;

&lt;p&gt;Create an interface and put a raw curl command inside &lt;code&gt;@JCurlCommand&lt;/code&gt;. The only "parameter convention" you need today is a &lt;code&gt;JQuickCurlReq&lt;/code&gt; — a simple &lt;code&gt;HashMap&amp;lt;String, Object&amp;gt;&lt;/code&gt; that carries variables for the command (more on that in a later post).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.anno.JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;EchoApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Any curl command starting with "curl" works as the annotation value.&lt;/span&gt;
    &lt;span class="nd"&gt;@JCurlCommand&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -X GET https://httpbin.org/get"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Run It from a &lt;code&gt;main&lt;/code&gt; Method
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;JCurlInvoker.createProxy(Class)&lt;/code&gt; builds a JDK dynamic proxy for your interface. Every call on that proxy parses the annotation's curl command, executes it, and converts the HTTP response body into the method's return type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.domain.req.JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.paohaijiao.executor.JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloCurl&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Create the proxy once — it is cheap and reusable.&lt;/span&gt;
        &lt;span class="nc"&gt;EchoApi&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JCurlInvoker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProxy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EchoApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. An empty request map for a command without variables.&lt;/span&gt;
        &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JQuickCurlReq&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Call your interface method like a local Java method.&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP response body:"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn compile &lt;span class="nb"&gt;exec&lt;/span&gt;:java &lt;span class="nt"&gt;-Dexec&lt;/span&gt;.mainClass&lt;span class="o"&gt;=&lt;/span&gt;HelloCurl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see httpbin's JSON echo — the &lt;code&gt;args&lt;/code&gt;, &lt;code&gt;headers&lt;/code&gt;, and &lt;code&gt;url&lt;/code&gt; that your request actually sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Accept-Encoding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gzip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"httpbin.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"okhttp/4.10.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/get"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole point: the request was described by curl syntax, yet executed by OkHttp in-process.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Just Happened Under the Hood?
&lt;/h2&gt;

&lt;p&gt;Three stages, all inside your JVM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse&lt;/strong&gt; — ANTLR4 lexes and parses the annotation string &lt;code&gt;curl -X GET https://httpbin.org/get&lt;/code&gt; into an abstract syntax tree of HTTP method, URL, headers, data, and options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build &amp;amp; execute&lt;/strong&gt; — a visitor walks the tree and produces a real OkHttp &lt;code&gt;Request&lt;/code&gt;; the executor sends it through OkHttp's client (connection pool, timeouts, retries, and interceptors included).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convert&lt;/strong&gt; — the raw response body is converted to the declared return type. Here it's &lt;code&gt;String&lt;/code&gt;, but &lt;code&gt;byte[]&lt;/code&gt;, &lt;code&gt;InputStream&lt;/code&gt;, custom POJOs, collections, &lt;code&gt;JResult&lt;/code&gt;, and raw &lt;code&gt;JQuickCurlResponseBody&lt;/code&gt; are all supported.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No &lt;code&gt;curl&lt;/code&gt; process, no shell, no command injection risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Backend Teams Care
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero translation bugs&lt;/strong&gt;: the command in your code is byte-for-byte the one you tested in Postman.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One format for everyone&lt;/strong&gt;: product docs, curl examples, and Java code stay in sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configurable where it counts&lt;/strong&gt;: timeouts, proxies, and retries can be layered on top without rewriting the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight&lt;/strong&gt;: Apache-2.0, pure Java, no external runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;JQuickCurl lets you declare HTTP calls the way you already think about them — as curl commands. In roughly ten lines of Java you have a typed, proxy-based HTTP client with no request-building boilerplate. The Hello-World example above is the smallest building block of a much larger toolkit: dynamic variables, XML API catalogs, conditional rendering, file transfer, batch runs, interceptors, and authentication are all covered in the upcoming posts of this series.&lt;/p&gt;

&lt;p&gt;Bookmark the repository — &lt;a href="https://github.com/dromara/jquick-curl" rel="noopener noreferrer"&gt;dromara/jquick-curl&lt;/a&gt; — and stay tuned for Post 2, where we wire this into a real Spring Boot application.&lt;/p&gt;

&lt;h1&gt;
  
  
  java #springboot #httpclient #opensource #java-library
&lt;/h1&gt;

</description>
      <category>curl</category>
      <category>java</category>
      <category>rest</category>
      <category>okhttp</category>
    </item>
  </channel>
</rss>
