What is the difference between hyper::Request::new and Request::builder for constructing HTTP requests?

Request::new creates a request with a body using default values for all other components, returning a complete Request immediately, while Request::builder returns a Builder that allows configuring individual request components (method, URI, headers, version) before constructing the final request, providing a fluent API for complex request construction. The choice between them depends on whether you need the simplest possible request construction or fine-grained control over request properties.

Basic Request Construction

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
 
fn basic_construction() {
    // Request::new: Create with just a body
    let body = Full::new(Bytes::from("hello world"));
    let request: Request<Full<Bytes>> = Request::new(body);
    
    // Default values:
    // - Method: GET
    // - URI: / (root path)
    // - Version: HTTP/1.1
    // - Headers: empty
    
    println!("Method: {}", request.method());  // GET
    println!("URI: {}", request.uri());        // /
    println!("Version: {:?}", request.version()); // HTTP/1.1
}

Request::new is the simplest way to create a request with default settings.

Using the Builder Pattern

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
 
fn builder_construction() {
    // Request::builder: Configure before building
    let request: Request<Full<Bytes>> = Request::builder()
        .method("POST")
        .uri("/api/users")
        .header("Content-Type", "application/json")
        .version(hyper::Version::HTTP_2)
        .body(Full::new(Bytes::from(r#"{"name": "Alice"}"#)))
        .unwrap();
    
    println!("Method: {}", request.method());  // POST
    println!("URI: {}", request.uri());        // /api/users
    println!("Version: {:?}", request.version()); // HTTP/2
}

Request::builder provides a fluent API for configuring individual components.

Method Configuration

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
 
fn method_configuration() {
    // Request::new: Always GET
    let get_request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    assert_eq!(*get_request.method(), Method::GET);
    
    // Request::builder: Set any method
    let post_request: Request<Full<Bytes>> = Request::builder()
        .method(Method::POST)
        .body(Full::new(Bytes::from("data")))
        .unwrap();
    assert_eq!(*post_request.method(), Method::POST);
    
    let custom_request: Request<Full<Bytes>> = Request::builder()
        .method("CUSTOM")
        .body(Full::new(Bytes::new()))
        .unwrap();
    assert_eq!(custom_request.method(), "CUSTOM");
    
    // Common methods
    let _get = Request::builder().method(Method::GET).body(Full::new(Bytes::new())).unwrap();
    let _post = Request::builder().method(Method::POST).body(Full::new(Bytes::new())).unwrap();
    let _put = Request::builder().method(Method::PUT).body(Full::new(Bytes::new())).unwrap();
    let _delete = Request::builder().method(Method::DELETE).body(Full::new(Bytes::new())).unwrap();
}

The builder allows setting any HTTP method; Request::new defaults to GET.

URI Configuration

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
use std::str::FromStr;
 
fn uri_configuration() {
    // Request::new: Always "/" 
    let default_request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    assert_eq!(default_request.uri(), "/");
    
    // Request::builder: Set any URI
    let request: Request<Full<Bytes>> = Request::builder()
        .uri("/api/v1/users/123")
        .body(Full::new(Bytes::new()))
        .unwrap();
    assert_eq!(request.uri(), "/api/v1/users/123");
    
    // Full URLs
    let request: Request<Full<Bytes>> = Request::builder()
        .uri("https://example.com/api/data?key=value")
        .body(Full::new(Bytes::new()))
        .unwrap();
    
    // URI components are parsed
    let uri = request.uri();
    println!("Scheme: {:?}", uri.scheme());     // Some("https")
    println!("Host: {:?}", uri.host());         // Some("example.com")
    println!("Path: {:?}", uri.path());         // "/api/data"
    println!("Query: {:?}", uri.query());       // Some("key=value")
}

Request::new defaults to "/" URI; builder allows any URI.

Header Configuration

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
 
fn header_configuration() {
    // Request::new: No headers
    let no_headers: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    assert!(no_headers.headers().is_empty());
    
    // Request::builder: Add headers fluently
    let request: Request<Full<Bytes>> = Request::builder()
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer token123")
        .header("Accept", "application/json")
        .header("User-Agent", "MyApp/1.0")
        .body(Full::new(Bytes::from(r#"{"data": "value"}"#)))
        .unwrap();
    
    // Access headers
    let headers = request.headers();
    assert_eq!(headers.get("Content-Type").unwrap(), "application/json");
    assert_eq!(headers.get("Authorization").unwrap(), "Bearer token123");
    
    // Multiple values for same header
    let request: Request<Full<Bytes>> = Request::builder()
        .header("Accept", "application/json")
        .header("Accept", "text/html")  // Adds second value
        .body(Full::new(Bytes::new()))
        .unwrap();
    
    // Headers are append by default
    let accept_values: Vec<_> = request.headers().get_all("Accept").iter().collect();
    assert_eq!(accept_values.len(), 2);
}

Request::new creates no headers; builder allows adding headers fluently.

HTTP Version Configuration

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Request, Version};
 
fn version_configuration() {
    // Request::new: HTTP/1.1
    let request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    assert_eq!(request.version(), Version::HTTP_11);
    
    // Request::builder: Choose version
    let http1_request: Request<Full<Bytes>> = Request::builder()
        .version(Version::HTTP_11)
        .body(Full::new(Bytes::new()))
        .unwrap();
    
    let http2_request: Request<Full<Bytes>> = Request::builder()
        .version(Version::HTTP_2)
        .body(Full::new(Bytes::new()))
        .unwrap();
    
    // Note: HTTP version is often determined by the client/connection
    // This setting is for constructing the request object
}

Request::new uses HTTP/1.1; builder allows specifying the version.

Body Configuration

use http_body_util::{Empty, Full};
use hyper::body::Bytes;
use hyper::Request;
 
fn body_configuration() {
    // Request::new: Body is the only parameter
    let body = Full::new(Bytes::from("request body"));
    let request: Request<Full<Bytes>> = Request::new(body);
    // Simple, direct body construction
    
    // Request::builder: Body via .body()
    let request: Request<Full<Bytes>> = Request::builder()
        .method("POST")
        .uri("/upload")
        .body(Full::new(Bytes::from("file contents")))
        .unwrap();
    
    // Empty body
    let request: Request<Empty<Bytes>> = Request::builder()
        .method("GET")
        .uri("/api/data")
        .body(Empty::new())
        .unwrap();
    
    // Both approaches require a body
    // Request::new makes body the sole parameter
    // Request::builder makes body the final .body() call
}

Both methods require a body; Request::new takes it as the parameter, builder uses .body().

Error Handling

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
 
fn error_handling() {
    // Request::new: Infallible, returns Request directly
    let request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    // Cannot fail, always returns valid Request
    
    // Request::builder: Returns Result
    let result: Result<Request<Full<Bytes>>, _> = Request::builder()
        .method("INVALID METHOD")  // Invalid method
        .body(Full::new(Bytes::new()));
    
    match result {
        Ok(request) => println!("Created: {:?}", request.method()),
        Err(e) => println!("Error: {}", e),  // Invalid method name
    }
    
    // Common error cases:
    // - Invalid method name
    // - Invalid URI
    // - Invalid header name or value
    // - Missing required components
    
    // Using unwrap for quick examples
    let request: Request<Full<Bytes>> = Request::builder()
        .method("POST")
        .uri("/api")
        .body(Full::new(Bytes::new()))
        .expect("valid request");
}

Request::new cannot fail; builder returns Result for validation errors.

When to Use Request::new

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Request;
 
fn when_to_use_new() {
    // Simple GET requests with body
    let request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    
    // Default values are acceptable
    // - GET method
    // - "/" URI
    // - No headers
    // - HTTP/1.1
    
    // Quick tests or simple clients
    fn simple_client() -> Request<Full<Bytes>> {
        Request::new(Full::new(Bytes::from("data")))
    }
    
    // When you only care about the body
    fn body_only_request(data: Bytes) -> Request<Full<Bytes>> {
        Request::new(Full::new(data))
    }
    
    // Simple cases where builder would be verbose
    // Request::new(body) vs Request::builder().body(body).unwrap()
}

Use Request::new for simple requests with acceptable defaults.

When to Use Request::builder

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request, Version};
 
fn when_to_use_builder() {
    // Non-GET methods
    let post_request: Request<Full<Bytes>> = Request::builder()
        .method(Method::POST)
        .body(Full::new(Bytes::from("data")))
        .unwrap();
    
    // Custom URIs
    let request: Request<Full<Bytes>> = Request::builder()
        .uri("/api/v1/users/123/profile")
        .body(Full::new(Bytes::new()))
        .unwrap();
    
    // Headers required
    let request: Request<Full<Bytes>> = Request::builder()
        .method(Method::POST)
        .uri("/api/data")
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer token")
        .body(Full::new(Bytes::from(r#"{"key": "value"}"#)))
        .unwrap();
    
    // HTTP/2
    let request: Request<Full<Bytes>> = Request::builder()
        .version(Version::HTTP_2)
        .body(Full::new(Bytes::new()))
        .unwrap();
    
    // Complex requests requiring multiple configuration steps
}

Use Request::builder when you need to configure multiple components.

Incremental Construction

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
 
fn incremental_construction() {
    // Builder can be stored and reused
    let base_builder = Request::builder()
        .method(Method::POST)
        .header("Authorization", "Bearer token123");
    
    // Clone and extend for different requests
    let users_request: Request<Full<Bytes>> = base_builder
        .clone()
        .uri("/api/users")
        .body(Full::new(Bytes::from(r#"{"name": "Alice"}"#)))
        .unwrap();
    
    let posts_request: Request<Full<Bytes>> = base_builder
        .clone()
        .uri("/api/posts")
        .body(Full::new(Bytes::from(r#"{"title": "Hello"}"#)))
        .unwrap();
    
    // Builder allows building multiple similar requests
}
 
fn conditional_headers(content_type: Option<&str>) -> Request<Full<Bytes>> {
    let mut builder = Request::builder()
        .method(Method::POST)
        .uri("/api/data");
    
    // Conditionally add headers
    if let Some(ct) = content_type {
        builder = builder.header("Content-Type", ct);
    }
    
    builder.body(Full::new(Bytes::from("data"))).unwrap()
}

Builder can be stored, cloned, and conditionally configured.

Reusable Request Templates

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
 
struct ApiClient {
    base_url: String,
    auth_token: String,
}
 
impl ApiClient {
    fn create_request(&self, method: Method, path: &str, body: Bytes) -> Request<Full<Bytes>> {
        // Template with common headers
        Request::builder()
            .method(method)
            .uri(format!("{}/{}", self.base_url, path))
            .header("Authorization", format!("Bearer {}", self.auth_token))
            .header("Accept", "application/json")
            .header("User-Agent", "MyApiClient/1.0")
            .body(Full::new(body))
            .unwrap()
    }
    
    fn get(&self, path: &str) -> Request<Full<Bytes>> {
        self.create_request(Method::GET, path, Bytes::new())
    }
    
    fn post(&self, path: &str, body: Bytes) -> Request<Full<Bytes>> {
        self.create_request(Method::POST, path, body)
    }
}
 
fn client_usage() {
    let client = ApiClient {
        base_url: "https://api.example.com".to_string(),
        auth_token: "secret".to_string(),
    };
    
    let get_request = client.get("users");
    let post_request = client.post("users", Bytes::from(r#"{"name": "Bob"}"#));
}

Builder pattern enables creating reusable request templates.

Modifying Existing Requests

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
 
fn modify_requests() {
    // Request::new creates a complete Request
    let mut request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    
    // Modify after creation
    *request.method_mut() = Method::POST;
    *request.uri_mut() = "/api/data".parse().unwrap();
    request.headers_mut().insert("Content-Type", "application/json".parse().unwrap());
    
    // This works but is less ergonomic than builder
    
    // Builder creates from scratch
    let request: Request<Full<Bytes>> = Request::builder()
        .method(Method::POST)
        .uri("/api/data")
        .header("Content-Type", "application/json")
        .body(Full::new(Bytes::new()))
        .unwrap();
}

Both approaches allow modifying the request, but builder is more ergonomic.

Complete Example: API Request

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request, Version};
 
fn api_request_example() {
    // Simple GET: Request::new is sufficient
    let get_request: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    
    // Complex API request: Builder is appropriate
    let api_request: Request<Full<Bytes>> = Request::builder()
        .method(Method::POST)
        .uri("https://api.example.com/v1/users")
        .version(Version::HTTP_11)
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer abc123")
        .header("Accept", "application/json")
        .header("X-Request-ID", "unique-id-123")
        .body(Full::new(Bytes::from(
            r#"{"name": "Alice", "email": "alice@example.com"}"#
        )))
        .unwrap();
    
    // Builder makes complex request construction readable
    // Each component is clearly specified
}

Complex requests benefit from builder's fluent API.

Comparison Summary

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
 
fn comparison() {
    // Request::new
    // - Takes body as parameter
    // - Returns Request directly (no Result)
    // - Default: GET, "/", HTTP/1.1, no headers
    // - Simple, minimal
    // - Use for simple GET requests
    
    let simple: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    
    // Request::builder
    // - Returns Builder, then .body() returns Result
    // - Configure method, URI, headers, version
    // - Fluent API
    // - Can fail with invalid configuration
    // - Use for complex requests
    
    let complex: Request<Full<Bytes>> = Request::builder()
        .method(Method::POST)
        .uri("/api/data")
        .header("Content-Type", "application/json")
        .body(Full::new(Bytes::from("data")))
        .unwrap();
    
    // Lines of code comparison:
    // Request::new: 1 line
    // Request::builder: N lines for N components
    
    // When defaults are acceptable:
    // Request::new(body) is simpler
    
    // When you need configuration:
    // Request::builder().method(...).uri(...).header(...).body(...)
}

Synthesis

Quick reference:

Aspect Request::new Request::builder
Body Parameter .body() method
Method GET (default) Configurable
URI "/" (default) Configurable
Headers Empty Configurable
Version HTTP/1.1 Configurable
Return type Request Result<Request, Error>
Use case Simple requests Complex requests

When to use each:

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Method, Request};
 
fn usage_guide() {
    // Use Request::new when:
    // - GET request with no special configuration
    // - Default values are acceptable
    // - Quick tests or prototypes
    // - Simplicity matters
    
    let simple: Request<Full<Bytes>> = Request::new(Full::new(Bytes::new()));
    
    // Use Request::builder when:
    // - Non-GET method required
    // - Custom URI needed
    // - Headers must be set
    // - HTTP version matters
    // - Building multiple similar requests
    
    let complex: Request<Full<Bytes>> = Request::builder()
        .method(Method::POST)
        .uri("/api/endpoint")
        .header("Authorization", "Bearer token")
        .body(Full::new(Bytes::from("data")))
        .unwrap();
}

Key insight: Request::new and Request::builder represent two approaches to request construction. Request::new(body) is the simplest possible construction—pass a body, get a request with sensible defaults (GET method, "/" URI, HTTP/1.1, no headers). It cannot fail and returns a complete Request directly. Request::builder() returns a Builder that provides a fluent API for configuring individual request components. Each configuration method (method(), uri(), header(), version()) returns the builder for chaining, and the final .body() call returns Result<Request, Error> because the configuration might be invalid. Use Request::new for simple GET requests or when defaults are acceptable. Use Request::builder for POST/PUT/DELETE methods, custom URIs, required headers, or when building request templates. The builder pattern enables incremental construction—you can store a partially-configured builder, clone it, and create multiple requests with common settings. Request::new is for simplicity; Request::builder is for control and flexibility.