Send SMS from Java with Twilio, MessageBird, or a notification API. Current Maven coordinates and code that compiles, plus how to pick between the three.
Updated Aug 7, 2026
Last updated: August 2026. All Maven coordinates verified against Maven Central on 2026-08-06.
Java has no built-in way to send a text message, so every option here means calling a third-party API. That part is short. What differs is how much of the machinery around the send you end up owning.
This guide covers three options with code that compiles against current SDK versions: Twilio, MessageBird, and a notification API layered over either.
A warning specific to Java: the versions matter. All three SDKs have shipped breaking changes since the tutorials most search results point at, and Java fails at compile time rather than silently. If a snippet does not compile, check the dependency version before you debug the code.
Twilio's Programmable Messaging API sends and receives SMS and MMS, reports delivery status through webhooks, and schedules sends.
What you need first
Add the dependency
<dependency><groupId>com.twilio.sdk</groupId><artifactId>twilio</artifactId><version>12.1.1</version></dependency>
Or with Gradle:
implementation("com.twilio.sdk:twilio:12.1.1")
Verified against com.twilio.sdk:twilio 12.1.1.
Send the message
import com.twilio.Twilio;import com.twilio.rest.api.v2010.account.Message;import com.twilio.type.PhoneNumber;public class TwilioSms {public static void main(String[] args) {Twilio.init(System.getenv("TWILIO_ACCOUNT_SID"),System.getenv("TWILIO_AUTH_TOKEN"));Message message = Message.creator(new PhoneNumber(System.getenv("CELL_PHONE_NUMBER")),new PhoneNumber(System.getenv("TWILIO_PHONE_NUMBER")),"Your order has shipped.").create();System.out.println(message.getSid() + " " + message.getStatus());}}
Argument order in Message.creator is to, then from, then body, which is easy to get backwards. Reading both numbers from the environment rather than passing literals keeps credentials and numbers out of your repository.
The status you get back is queued or accepted, not delivered. To learn what actually happened, either fetch the message again by SID or register a status callback webhook.
Where it gets thin: one provider, message copy compiled into your application, and delivery events arriving as raw webhooks you store and interpret yourself.
MessageBird bills per message with rates that vary by destination country. Its Java SDK is maintained, which is worth saying because the company's Node.js SDK is not: MessageBird now trades as Bird, and its newer platform SDK is TypeScript-only. For Java, the messagebird-api library remains the supported path.
What you need first
Add the dependency
<dependency><groupId>com.messagebird</groupId><artifactId>messagebird-api</artifactId><version>6.4.0</version></dependency>
Verified against com.messagebird:messagebird-api 6.4.0.
Send the message
import com.messagebird.MessageBirdClient;import com.messagebird.MessageBirdService;import com.messagebird.MessageBirdServiceImpl;import com.messagebird.objects.Message;import com.messagebird.objects.MessageResponse;public class MessageBirdSms {public static void main(String[] args) {MessageBirdService service =new MessageBirdServiceImpl(System.getenv("MESSAGEBIRD_API_KEY"));MessageBirdClient client = new MessageBirdClient(service);// Constructor order is originator, body, recipients.// The originator is your sender ID, not the message text.Message message = new Message("AcmeInc","Your order has shipped.","+15558675309");try {MessageResponse response = client.sendMessage(message);System.out.println(response.getId());} catch (Exception e) {System.err.println("Send failed: " + e.getMessage());}}}
The Message constructor takes originator, body, then recipients, in that order. The originator is the sender identity that shows up on the recipient's phone, so it belongs as a sender ID or number, not the message content. recipients also accepts a List if you are sending to several numbers in one call.
sendMessage returns a MessageResponse carrying the message ID. Capture it; discarding the return value throws away your only handle for looking the message up later.
Where it gets thin: same layer as Twilio, so the same gaps. Also a smaller Java community, so unusual failures take longer to diagnose.
Both options above solve delivery. Here is what usually turns up in the fortnight afterward:
A notification API such as Courier sits above your SMS provider and handles those. You keep Twilio or MessageBird delivering, and your Java code stops knowing which one it is.
What the extra layer gives you:
Set it up
Create a free account, then connect an SMS provider under Channels using the credentials that provider already gave you. This is the same Twilio or MessageBird account from the sections above; you are not changing who delivers.
Add the dependency
<dependency><groupId>com.courier</groupId><artifactId>courier-java</artifactId><version>4.25.0</version></dependency>
Or with Gradle:
implementation("com.courier:courier-java:4.25.0")
Verified against com.courier:courier-java 4.25.0. Do not copy a 1.x snippet; the API is entirely different and will not compile.
Send the message
import com.courier.client.CourierClient;import com.courier.client.okhttp.CourierOkHttpClient;import com.courier.models.ElementalContentSugar;import com.courier.models.UserRecipient;import com.courier.models.send.SendMessageParams;public class CourierSms {public static void main(String[] args) {// Reads COURIER_API_KEY from the environment.CourierClient client = CourierOkHttpClient.fromEnv();SendMessageParams params = SendMessageParams.builder().message(SendMessageParams.Message.builder().to(UserRecipient.builder().phoneNumber("+15558675309").build()).content(ElementalContentSugar.builder().title("Order update").body("Hi Erika, your order has shipped.").build()).build()).build();var response = client.send().message(params);System.out.println(response.requestId());}}
That sends content defined inline. The version you will run in production references a template you published in Courier and addresses the user rather than a raw number:
SendMessageParams params = SendMessageParams.builder().message(SendMessageParams.Message.builder().to(UserRecipient.builder().userId("user-123").build()).template("ORDER_SHIPPED").build()).build();
Nothing in that call names SMS. Routing and the user's stored preferences decide the channel, so moving a notification to push, or sending both, is configuration rather than a recompile.
The SDK throws typed unchecked exceptions rather than returning error codes, so you catch by status:
try {var response = client.send().message(params);System.out.println(response.requestId());} catch (com.courier.errors.RateLimitException e) {// back off and retry} catch (com.courier.errors.BadRequestException e) {// your payload is wrong; retrying will not helpSystem.err.println(e.getMessage());}
CourierServiceException is the base for HTTP errors, with subclasses per status code. Retry RateLimitException and InternalServerException; do not retry BadRequestException, which will fail identically every time.
Where it gets thin: one more service in the path, and one more concept before your first send. It is also the wrong tool for two-way SMS conversations, which belong with your provider's inbound APIs.
| Twilio | MessageBird | Notification API | |
|---|---|---|---|
| Maven artifact | com.twilio.sdk:twilio 12.1.1 | com.messagebird:messagebird-api 6.4.0 | com.courier:courier-java 4.25.0 |
| Client setup | Twilio.init(sid, token) | new MessageBirdClient(service) | CourierOkHttpClient.fromEnv() |
| Send call | Message.creator(...).create() | client.sendMessage(message) | client.send().message(params) |
| Reads credentials from env | you pass them | you pass them | yes, fromEnv() |
| Typed exceptions per status | partial | generic | yes |
| Other channels | SMS, WhatsApp, voice, email | SMS, WhatsApp, voice | whatever you connect |
| Templates outside your code | no | no | yes |
| Preferences and opt-outs | you build it | you build it | built in |
| Provider failover | n/a | n/a | yes, across providers |
| Best for | first send, widest community | per-country pricing | SMS inside a product flow |
If you need one text sent this afternoon, add the Twilio dependency and stop reading. If SMS is becoming a feature of a JVM application, the argument for the notification layer is stronger than in most languages: getting message copy out of compiled code and into templates means wording changes stop requiring a release.
FAQ
Keep exploring
One API, every channel
Courier gives you one API for email, SMS, push, and chat, with templates, routing, retries, and delivery logs built in.
Last updated Aug 7, 2026. Code samples are illustrative; provider APIs and pricing change over time, so check each provider’s docs before relying on them.
© 2026 Courier. All rights reserved.