Backend Development 20 min read

UniHttp – A Declarative HTTP Interface Integration Framework for Java

This article introduces UniHttp, a declarative Java HTTP client framework that simplifies third‑party API integration by using annotations to generate request metadata, supports various parameter types, lifecycle hooks, custom processors, and provides practical enterprise‑level examples with full code snippets.

Architecture Digest
Architecture Digest
Architecture Digest
UniHttp – A Declarative HTTP Interface Integration Framework for Java

Introduction

In enterprise projects, using traditional programmatic HTTP clients such as HttpClient or OkHttp often leads to scattered, hard‑to‑maintain code when integrating many third‑party APIs. UniHttp offers a declarative approach that lets developers call remote HTTP services as if they were local methods.

Quick Start

1. Add Dependency

<dependency>
  <groupId>io.github.burukeyou</groupId>
  <artifactId>uniapi-http</artifactId>
  <version>0.1.0</version>
</dependency>

2. Define Interface

@HttpApi(url = "http://localhost:8080")
public interface UserHttpApi {
    @GetHttpInterface("/getUser")
    BaseRsp
getUser(@QueryPar("name") String param, @HeaderPar("userId") Integer id);

    @PostHttpInterface("/addUser")
    BaseRsp
addUser(@BodyJsonPar Add4DTO req);
}

3. Use the API

@Service
public class UserAppService {
    @Autowired
    private UserHttpApi userHttpApi;

    public void doSomething() {
        userHttpApi.getUser("jay", 3);
    }
}

Annotations Overview

UniHttp provides a family of annotations to describe request parts:

@HttpApi – marks an interface as an HTTP API and sets the base URL.

@GetHttpInterface / @PostHttpInterface / @PutHttpInterface / @DeleteHttpInterface – specify HTTP method and path.

@QueryPar – puts a parameter into the query string.

@HeaderPar – adds a request header.

@CookiePar – adds a cookie.

@BodyJsonPar – sends a JSON body (content‑type application/json ).

@BodyFormPar – sends a form‑urlencoded body (content‑type application/x-www-form-urlencoded ).

@BodyMultiPartPar – sends multipart/form‑data, useful for file uploads.

@BodyBinaryPar – sends raw binary data (content‑type application/octet-stream ).

@ComposePar – groups multiple @Par annotations inside a single object to reduce method parameters.

Lifecycle Hooks (HttpApiProcessor)

Implement HttpApiProcessor<YourAnnotation> to customize the request/response lifecycle. The main hooks are:

postBeforeHttpMetadata – modify the request before it is sent (e.g., add signatures, extra query parameters).

postSendHttpRequest – replace the default sending logic, log requests, or inject dynamic tokens.

postAfterHttpResponseBodyString – process the raw response body (e.g., decrypt).

postAfterHttpResponseBodyResult – manipulate the deserialized result.

postAfterMethodReturnValue – AOP‑style post‑processing of the proxy method’s return value.

Custom HTTP Client Configuration

@Configuration
public class CustomConfiguration {
    @Bean
    public OkHttpClient myOkHttpClient(){
        return new OkHttpClient.Builder()
                .readTimeout(50, TimeUnit.SECONDS)
                .writeTimeout(50, TimeUnit.SECONDS)
                .connectTimeout(10, TimeUnit.SECONDS)
                .connectionPool(new ConnectionPool(20,10, TimeUnit.MINUTES))
                .build();
    }
}

Enterprise‑Level Integration Example

The article demonstrates how to create a channel‑specific annotation ( @MTuanHttpApi ) that automatically adds an appId query parameter, generates a cryptographic sign header, and injects a token and sessionId obtained from a separate authentication call.

@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@HttpApi(processor = MTuanHttpApiProcessor.class)
public @interface MTuanHttpApi {
    @AliasFor(annotation = HttpApi.class)
    String url() default "${channel.mtuan.url}";
    String appId() default "${channel.mtuan.appId}";
}

@Slf4j
@Component
public class MTuanHttpApiProcessor implements HttpApiProcessor
{
    @Value("${channel.mtuan.publicKey}") private String publicKey;
    @Value("${channel.mtuan.appId}") private String appId;
    @Autowired private Environment environment;
    @Autowired private WeatherApi weatherApi;

    @Override
    public HttpMetadata postBeforeHttpMetadata(HttpMetadata meta, HttpApiMethodInvocation
inv) {
        String resolvedAppId = environment.resolvePlaceholders(inv.getProxyApiAnnotation().appId());
        meta.putQueryParam("appId", resolvedAppId);
        String sign = createSignKey(meta.getHttpUrl().getQueryParam(), meta.getBody());
        meta.putHeader("sign", sign);
        return meta;
    }

    private String createSignKey(Map
query, HttpBody body){
        // simplified pseudo‑code for signature generation
        String qs = query.entrySet().stream()
                .map(e -> e.getKey()+"="+e.getValue())
                .collect(Collectors.joining(";"));
        String bodyStr = (body instanceof HttpBodyJSON) ? body.toStringBody() : "";
        String raw = publicKey + qs + bodyStr;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            return new String(md.digest(raw.getBytes()));
        } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); }
    }

    @Override
    public HttpResponse
postSendHttpRequest(HttpSender sender, HttpMetadata meta) {
        // obtain token and sessionId before the real request
        HttpResponse
tokenResp = weatherApi.getToken(appId, publicKey);
        String token = tokenResp.getBodyResult();
        String sessionId = tokenResp.getHeader("sessionId");
        meta.addCookie(new Cookie("token", token));
        meta.addCookie(new Cookie("sessionId", sessionId));
        log.info("Sending request {} {}", meta.getHttpUrl().toUrl(), meta.toHttpProtocol());
        return sender.sendHttpRequest(meta);
    }

    @Override
    public Object postAfterHttpResponseBodyResult(Object body, HttpResponse
resp, Method method, HttpMetadata meta) {
        if (body instanceof BaseRsp) {
            ((BaseRsp)body).setCode(999);
        }
        return body;
    }
}

With this processor, every request to the weather service automatically includes the required appId , a cryptographic sign , and dynamic authentication cookies, while the response code is normalized to 999 .

Conclusion

UniHttp provides a high‑level, annotation‑driven way to integrate third‑party HTTP services, reduces boilerplate, supports rich parameter binding, and offers extensible lifecycle hooks for enterprise scenarios such as signing, token management, and custom client configuration.

backendJavaSpringHTTPAPIDeclarative
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.