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.
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 BootRestTemplate. - Store credentials in environment variables, not hardcoded strings.
- The
recipientsfield 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
- textbee app installed on your Android device and linked to your account: download and quickstart
- Device ID and API key from the dashboard at textbee.dev
- Java 11+ for the native
HttpClientexample; Java 8+ for OkHttp - On Android 15 or 16? You may need to manually enable SMS permissions once
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:
export TEXTBEE_DEVICE_ID="your-device-id"
export TEXTBEE_API_KEY="your-api-key"
javac TextbeeSender.java && java TextbeeSenderNote 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.
Option 1b: Native HttpClient with Jackson (recommended for production)
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
- Full API reference — send, receive, webhooks, device management
- Receive SMS with webhooks — inbound SMS in a Java server
- Send SMS from Go, Python, PHP — same API, other languages
- Pricing — free tier available
Download textbee and send your first Java SMS in under 5 minutes.
You may also like

9 Best Twilio Alternatives in 2026 (Cheaper SMS APIs Compared)
Compared: Plivo, Telnyx, Vonage, Bird, Sinch, Bandwidth, AWS SNS, ClickSend, and textbee. Real 2026 pricing, pros, and cons for each alternative.

Cost-Effective SMS Solutions: Why textbee Beats Traditional SMS APIs in 2026
Traditional SMS APIs charge per segment, carrier fees, number rental, and 10DLC. An Android SMS gateway like textbee replaces all that with one flat fee.

Developers in Emerging Markets: Twilio Doesn't Support Your Country? textbee Works Anywhere Android Works
When CPaaS onboarding, pricing, or coverage blocks you, an Android phone on a SIM is often the fastest path to SMS for developers underserved by Twilio.