Plans from $9.99/mo.View Plans
S
java
tutorial
api
sms
android
spring-boot

Send SMS from Java: No Twilio, Just Your Android Phone

Send real SMS from Java using textbee and your Android phone as the gateway. Native HttpClient, OkHttp, and Spring Boot examples, zero per-message fees.

TT

textbee team

6 min read
Share

TL;DR

  • textbee's REST API works from any Java version with HTTP capability — no SDK or Twilio account needed.
  • Three options: native HttpClient (Java 11+, zero dependencies), OkHttp (Maven/Gradle), or Spring Boot RestTemplate.
  • Store credentials in environment variables, not hardcoded strings.
  • The recipients field is a JSON array — one request sends to multiple numbers.
  • No per-message API fee: messages route through the SIM in your Android phone.

You don't need a Twilio account or a rented virtual number to send SMS from Java. If you have an Android phone with a working SIM, textbee turns it into an SMS gateway your Java code can call over a plain REST API. The message goes out from your real phone number.

Before you start

Option 1: Native HttpClient (Java 11+, no dependencies)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class TextbeeSender {

    private static final HttpClient CLIENT = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();

    public static String sendSms(String to, String message) throws Exception {
        String deviceId = System.getenv("TEXTBEE_DEVICE_ID");
        String apiKey   = System.getenv("TEXTBEE_API_KEY");

        String url  = "https://api.textbee.dev/api/v1/gateway/devices/" + deviceId + "/send-sms";
        String body = String.format(
            "{\"recipients\": [\"%s\"], \"message\": \"%s\"}",
            to, message.replace("\"", "\\\"")
        );

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .timeout(Duration.ofSeconds(10))
                .header("Content-Type", "application/json")
                .header("x-api-key", apiKey)
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();

        HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            throw new RuntimeException("SMS send failed (HTTP " + response.statusCode() + "): " + response.body());
        }

        return response.body();
    }

    public static void main(String[] args) throws Exception {
        String result = sendSms("+15551234567", "Hello from Java!");
        System.out.println(result);
    }
}

Run with environment variables set:

Shell
export TEXTBEE_DEVICE_ID="your-device-id"
export TEXTBEE_API_KEY="your-api-key"
javac TextbeeSender.java && java TextbeeSender

Note on string formatting: The String.format approach above works for simple messages. For production, use a JSON library (Gson, Jackson, or org.json) to avoid escaping bugs with special characters in message bodies.

import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;

public class TextbeeSender {

    private static final HttpClient  CLIENT = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10)).build();
    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static Map<?, ?> sendSms(String to, String message) throws Exception {
        String deviceId = System.getenv("TEXTBEE_DEVICE_ID");
        String apiKey   = System.getenv("TEXTBEE_API_KEY");

        String url  = "https://api.textbee.dev/api/v1/gateway/devices/" + deviceId + "/send-sms";
        String body = MAPPER.writeValueAsString(Map.of(
            "recipients", List.of(to),
            "message",    message
        ));

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .timeout(Duration.ofSeconds(10))
                .header("Content-Type", "application/json")
                .header("x-api-key", apiKey)
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();

        HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            throw new RuntimeException("SMS send failed (HTTP " + response.statusCode() + "): " + response.body());
        }

        return MAPPER.readValue(response.body(), Map.class);
    }
}

Add Jackson to your pom.xml:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

Or build.gradle:

implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'

Option 2: OkHttp (Java 8+)

OkHttp works on Java 8 and is popular in Android and backend Java projects:

import okhttp3.*;
import com.google.gson.Gson;
import java.util.List;
import java.util.Map;

public class TextbeeSender {

    private static final OkHttpClient CLIENT = new OkHttpClient();
    private static final Gson         GSON   = new Gson();
    private static final MediaType    JSON   = MediaType.get("application/json");

    public static String sendSms(String to, String message) throws Exception {
        String deviceId = System.getenv("TEXTBEE_DEVICE_ID");
        String apiKey   = System.getenv("TEXTBEE_API_KEY");

        String url  = "https://api.textbee.dev/api/v1/gateway/devices/" + deviceId + "/send-sms";
        String body = GSON.toJson(Map.of("recipients", List.of(to), "message", message));

        Request request = new Request.Builder()
                .url(url)
                .addHeader("x-api-key", apiKey)
                .post(RequestBody.create(body, JSON))
                .build();

        try (Response response = CLIENT.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new RuntimeException("SMS send failed (HTTP " + response.code() + "): " + response.body().string());
            }
            return response.body().string();
        }
    }
}

Add OkHttp and Gson to pom.xml:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.12.0</version>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

Option 3: Spring Boot RestTemplate

In a Spring Boot application, RestTemplate integrates cleanly with your existing configuration:

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.*;
import org.springframework.beans.factory.annotation.Value;
import java.util.List;
import java.util.Map;

@Service
public class SmsService {

    private final RestTemplate restTemplate;

    @Value("${textbee.device-id}")
    private String deviceId;

    @Value("${textbee.api-key}")
    private String apiKey;

    public SmsService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public Map<?, ?> sendSms(String to, String message) {
        String url = "https://api.textbee.dev/api/v1/gateway/devices/" + deviceId + "/send-sms";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("x-api-key", apiKey);

        Map<String, Object> body = Map.of(
            "recipients", List.of(to),
            "message",    message
        );

        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);

        ResponseEntity<Map> response = restTemplate.exchange(
            url, HttpMethod.POST, entity, Map.class
        );

        return response.getBody();
    }
}

Add to application.properties (reference environment variables):

textbee.device-id=${TEXTBEE_DEVICE_ID}
textbee.api-key=${TEXTBEE_API_KEY}

Register a RestTemplate bean in your configuration:

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

What the API returns

On success:

{
  "data": {
    "success": true,
    "message": "SMS added to queue for processing",
    "smsBatchId": "abc123xyz",
    "recipientCount": 1
  }
}

On failure, you get a non-2xx HTTP status. The response body contains an error message. All three examples above throw a RuntimeException with the status code and body — catch it in your calling code and handle accordingly (retry logic, alerting, fallback).

Sending to multiple recipients

Pass multiple numbers in the recipients list:

String body = MAPPER.writeValueAsString(Map.of(
    "recipients", List.of("+15551111111", "+15552222222", "+15553333333"),
    "message",    "Reminder: team meeting at 3pm today."
));

All recipients get the same message body in one API call and one batch ID.

Things worth knowing

Phone number format: Use E.164 (+15551234567). Local formats may work for same-country sends but E.164 is unambiguous and always correct.

Thread safety: HttpClient (Java 11+) and OkHttpClient are thread-safe and should be shared as singletons — create once, reuse everywhere. Don't instantiate a new client per request.

No per-message fee: Messages route through your own Android SIM. See pricing for the flat subscription tiers.

Opt-in compliance: Read the SMS compliance checklist before sending to lists.

Frequently asked questions

Is there a Java SDK for textbee?

Not yet. The REST API is simple enough — the examples above cover all common use cases. The API docs cover inbound webhooks, device status, and batch operations for anything beyond single sends.

What Java version is required?

Option 1 (native HttpClient) requires Java 11+. Options 2 (OkHttp) and 3 (Spring Boot) work on Java 8+. For new projects, Java 17 LTS or Java 21 LTS is recommended.

Can I use this in a Spring Boot application?

Yes — Option 3 above integrates directly into the Spring dependency injection model. For async sends (non-blocking), wrap the RestTemplate call in a @Async method or use WebClient from Spring WebFlux instead.

How do I handle async sending in Java?

Wrap the send call in a CompletableFuture:

CompletableFuture.runAsync(() -> {
    try {
        sendSms("+15551234567", "Your order has shipped!");
    } catch (Exception e) {
        log.error("SMS send failed", e);
    }
});

Or use Spring's @Async annotation on the service method for cleaner integration.

Keep going

Download textbee and send your first Java SMS in under 5 minutes.