<?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>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>
