Skip to main content

Webinars

Developing with Bandwidth: Java 101

Come hangout with the Bandwidth Developer Experience team as we dive into Java and demo what’s new with our Voice & Messaging SDKs and APIs. We’ll also share what’s new with our Developer Experience, plus give you a sneak peek at our roadmap for future Java SDK support.

What you’ll learn

  • Install and configure your development environment in Java
  • Setup your Bandwidth Account to start using the new APIs
  • Send and receive group MMS
  • Manage inbound and outbound phone calls

Webinar Transcription:

INSTRUCTOR: Thanks for watching today's webinar. We're going to be going over our bandwidth Java SDK, building a voice and messaging sample app. We're going to build a relatively simple app using the Spark Java framework. We're going to build an app that receives a text message and creates a response based on the text that was sent, and then we're going to answer an inbound phone call and play a little arithmetic game to emulate what it would be like to receive digits or prompt someone to please press one to accept the call, or press one to hear your messages, if you will. So with that, I'm going to get started. We've got our bandwidth examples pulled up here. This is a GitHub repo that you can find from our documentation site. If you go to bandwidth.com, you can see this link down here, developer tools, bandwidth examples, GitHub repo. Going to that link takes us to here. And specifically, we're going to be looking at Java. And we're going to be using the boilerplate application that's already written here. It does need a few environment variables to get going, and we'll go through where those are at the bandwidth dashboard and how to create your applications and get your token in secrets. But essentially, we need an account ID from your bandwidth account. We need your messaging API token in secret, which will be used as basic off. However, it's a bit different than your login credentials or your voice API username and password. And we also need the voice application ID. As part of the full out with bandwidth, we do require that the callbacks are sent to a publicly addressable URL. So we'll be talking about using ngrok in order to stand up a URL that points to a local host. So right now, we have our boilerplate. If we dive in to the source code here, it's a little bit more featured than what I have in my ontology app right now, but we're going to be deleting some things, and then adding a lot to it today. So if we first look at it, we've got really just in the boilerplate GitHub, it goes through-- it just grabs the environment variables for voice and messaging. And then it creates the bandwidth client. This is going to be a builder that gives us a client to access our API, specifically for voice and messaging. We'll be adding our phone number API to this later this year. So within this simple app, we have a couple of paths to find. We have to get to this route. This is going to return hello, world if someone visits that domain we have. We're going to have an endpoint to post to create an outbound message. So this would be a server that you would have setting up to. You would send a request to send the message. This would have all your bandwidth credential saved in the server here and would send that message out to the world. We're not going to be using this. We're not going to be initiating outbound text. We're always going to be responding. Same thing for a phone call. And as I said before, we're only responding to inbound calls. However, what we do need, we do need this post to the callbacks messaging. So voice and messaging both use callbacks, as I mentioned before. Specifically for messaging, both inbound messages and outbound message status updates go to the same URL. So there's only one URL we need for messaging. For voice, you do have the flexibility of setting up an inbound voice URL, as well as an outbound voice URL when you create the call. You can set it to go to a different path on your server. And then for BXML, we're not going to be touching this specific path today. We'll be using the inbound voice path to generate our BXML. So with that, let's actually dive into the sample output we're going be going today. If we go to the Pull Requests, here this may not be here depending on when you watch it, but we're going to be updating the boilerplate for the webinar. This is going to be off of this branch, Update boilerplate for webinar. Kind of self-explanatory. But then let's go just navigate to this and go to the tutorial that's been put together under this boilerplate app right now. So this is going to be our more full-featured sample from the boilerplate that we're covering today. It does have some pre-reqs. Obviously, you need a bandwidth account to get going. We've talked about environment credentials. But let's go over the requirements of the app. So we need you to be able to text that phone number. We're going to order a phone number. When you text that phone number the word dog, we're going to send you back a picture of a dog. If you text that number anything else, we're just going to give you a generic, here you go, response. And then if you call that phone number, you're going to be asked to play a game. It's a very simple arithmetic game. You'll be asked some math question. You respond using the dial pad. And based on your response, we'll check if it was a positive if you got it right, or negative if you got it wrong, and play an audio file, either celebrating your success or sad tromboning your failure. So with that, let's go and just log in to the bandwidth dashboard, and we're going to need our account ID. We're goign to need our tokens and secrets. And we're going to need an application for both voice and messaging, as well as your voice API using your voice API password. To get logged in to the dashboard, we're going to go to dashboard.bandwidth.com. And I'm going to log in with my account right now. And I am presented with my nice user interface here. So the first thing we need to do is go ahead and create some applications. So I'm going to head to the Applications tab. And for what it's worth, I've mostly already created these applications to use during this webinar. So I'm going to be reusing those just to keep everything clean. However, when you're starting from scratch, you do have the ability just to create a new application. Give it your own name. You can choose between messaging and voice. We'll go over that in just a second. And then the ID will be generated whenever you click the Create application. So in order to get my publicly addressable callback URL, I need to run a program called ngrok. I'm actually goign to get my terminal pulled up over here. And if I just look at my history, the default port for Spark Java is going to be 4567. And then if I were to click the Run button here, so I'm actually going to start up my little Spark server and I visit this URL, we should get Hello, world. There we go. And you can see over here at my ngrok terminal, there's my browser making the request to that root path. We could now make a request to Hello, world. And you can see the path getting requested right there. However, we don't have anything to return on a Hello, world path, so it returns 404. So that's just kind of a proof of concept. However, it does give us our ngrok URL. So this is going to be publicly addressable for bandwidth this in callbacks. So we're going to pop in to our Java webinar messaging application. And I've already got that mostly filled in, but we're going to update that right now. So I've got my full callback URL for callbacks messaging. We can see that's what we have here. So we now send a text message to any numbers that are associated with this location that use this application. We will be receiving those callbacks to the server. Cool. Go ahead and click Save. And then our application ID is going to be right here. So we can copy that, and then just for the sake, I-- well, actually, I don't have that defined in here. We will get to that in a minute. All right. I'm going to just open up, Sublime Text and save that right now. So that's going to be my messaging application. I'll need that later. And then we're going to do the same thing for voice. So as I said before, we need to paste our call back URL for our voice inbound, make sure we get that right. The status callback URL is going to be used for non-actionable events. So any time that we have a hang-up event or a disconnect event, anything that you're unable to respond with BXMl, basically, to continue that phone call. So if the phone call reaches a terminal state and you did not respond to the initial call back inbound voice request, you can set that up to send to the callback-- the status callback URL. This is going to be things like invalid BXMI. No. Any time we want to give you updates about your phone call, that are not able to affect the actual phone call itself. Then finally, I'm just going to send everything as a POST request and go ahead and click Save. And I have my voice application ID here. So I will save this. All right, cool. And those are different. Just making sure. Sometimes, the copy-paste doesn't work, right? So now, we both have-- I have voice and messaging application, Java Webinar Messaging, Java Webinar Voice. I need a location to associate these two. So if we head to our account page here and we go to our locations-- sorry, before we do that, we need our API tokens and secrets. We're going to get that at the Applications tab. This right here, this 990078, this is going to be my account ID. So this is unique to mine. You will have a different account ID. You can also see it in the URI up here. So we don't actually need this for URI. We already have that. We just need our account ID. Account ID. It's going to be this. Cool. And then for API credentials, anything here right now under this application page is specifically for messaging. So we click this API credentials page. We have tokens and secrets. I'm just going to create a new one for example. We have our API token there. This is used as the username for a basic off authentication flow. This is the secret. Now that I've shown these to you, I can't keep them. So I'm going to delete that. Yes, I'm sure. So you can have those set for each one of your developers. They are unique. However, as you see, once you generate them and close this window, you cannot see that secret ever again. So it's important to kind of keep up with what your secrets are matched with what tokens if that's important to you. Otherwise, you would need to generate a new token secret pair every time you lose that secret, because it's unrecoverable for bandwidth at this point. Finally, we need our voice API credentials. And so those are going to be your normal log in that you have for dashboard.bandwidth.com. So whatever your email address or username and your password you use to log in to dashboard, you can use those to access the voice product if you have that. If you would like to create a specific user for your API, that's what we recommend. You're able to manage your users on your account under the account and user setting. And in this case, I have an example here. I have an API user for GitHub API, and this is set to API only. So I do not have to set my password over again. It's able to allow me to build out programmatic applications without needing the same login for my dashboard. You can talk to your support rep if you're curious about more information on that. So finally, at this point, we're ready to get to no notifications. Let's try that again, and let's turn night shift off. So now, we have no notifications going on. We can hop in to creating our location. So we now have our applications, we have our account ID, we have our tokens and secrets, we have our login credentials, we have our voice API credentials. We need a location. So under the account page, we head to the Locations tab. And you can create a new location. I'll do that for example here and make sure that it's not my default location. I'll just do demo location, 123 for now. Let's just take that out just in case. And I want the protocol, actually, to be HTTP voice for voice protocol. And I need this to be my Java boilerplate, voice Java-- webinar voice, I'm sorry. And then for SMS settings, I need to go ahead and enable those. I don't really care about texting to or from shortcodes, and I don't really care about interacting with toll-free numbers either. So I'm just going to leave these to disabled. But I'm going to be using the HTTP protocol. And I am going to be using B2 messaging. And I will be using my Java webinar messaging application. And then finally, for MMS, it's going to be the same. I just have to enable it. I have no options but to keep what I have set up here, which makes complete sense. So if I were to create this location, I'm not going to, because I already have one. But if I were going to create this location, we'd be good to go. So I'm just actually going to hop into my Java webinar example here. And I already have a phone number on this account, but let's go and just delete this number for now. Yeah, so we're going to go back now to my account. No, I don't need to-- yeah I can get the numbers. And I want to go ahead and buy a 919 number. Let's go here. I'm going to grab this 756 number. It's going to continue. This is going to go under my sub-account. This is going to go to the Java webinar. And I'm just going to purchase this. Absolutely. All right, so now, I do have this phone number ready to go. And if dial pad and everything is working correctly, if I pull up this and I click Send a message, typed that in here. And let's do 123. I don't know if I can do that. All right, cool, we see there's a inbound post request. And if we were to inspect this right here using the ngrok viewer, we can see there is a inbound message received request from my dial part number to my balance number that I just ordered. So now, I can see that I'm receiving those inbound message events that were happening on that phone number. Nothing responded because we haven't built it yet, but we're starting to get the web hooks. We're almost there. And then similarly, if I were to call that number, we get an inbound call. And there's again nothing wired up there. So we just responded, but we can see there is our inbound call webhook sent from bandwidth to my server here. All right, so at this point, we've spent a lot of time, but we have our account ready to set up. But we have it set up ready to start sending and receiving text messages, and answering some phone calls and quizzing people on their math skills. All right, let's see how we build this app. So we've gone about what we can do. We have ngrok up and running. We're going to pull up the Spark Java notes. Apparently, I've got the URL wrong. Apologies here. Spark Java note. This is a very quick easy framework. It sets up relatively simple path, a well-built-- an architect a Java app would be a little bit more complex than what we're covering today. And so we've got the bandwidth SDK. It's going to be available on Maven, where currently version 1.2.1. We follow pretty stringent release cycle that as micro-- or we use minor releases for new functionality and patch releases for bug fixes and small usability tweaks with the major releases indicating breaking changes. It's a semantic versioning scheme. And finally, we talked about this, remove the POST request there. So let's actually dive into the code. So we're going to be copying and pasting a lot of the code from the built tutorial here just to make sure that we get everything right. But I can talk through what we're doing and what's going on. So we do need our bandwidth controllers. So I'm going to just copy this right here. And I'm just going to paste this in before we enter our main method, so we have these available to us. And then as we go, I will do my best to realign things as we go. So one thing I did say before is that my messaging application ID did change from what I have saved as an environment variable. So for demo purposes, I am going to set my bandwidth application ID to the string there. And again, in a more sophisticated Java application, you would want to be using constants or environment variables preferably so that they're easy to change and not tied to source. However, for the purposes of a demonstration, I think it would be fine. And then the same for our voice application ID. I have a new you URI for that or a new ID for that. There needs to be a system request. Cool. All right, so now I have my account ID. I do have that already saved as an environment variable, same from a messaging account ID. They're usually going to be the same. For my tokens, and secrets, and passwords, those are all safe to my environment variable. So I'm not going to show those to you. Because otherwise, I have to reset all my passwords. It's no fun for me. All right, at this point, I have grabbed my account credentials. I have all that ready to go. And then we're going to use them and create the bandwidth client. Every single product that we have hangs off as a namespace on this Java client with phone numbers coming soon. So for now, we need to create a messaging controller. This is what gives us access to our messaging API, and then the same for our voice controller. So we'll be sending API requests with this messaging controller to create text messages, and then the same for voice using the voice controller. Finally, we also have, in our import statements, which will get up to, a lot of these unused, we have our BXML verbs. This is what's going to allow us to build BXML programmatically without having to write the XML directly. So we can start editing and sending that around there. So now, at this point, all we've really done is set up the bandwidth clients and the controllers to send and receive text messages and phone calls. We're going to start, actually, coding up some stuff. So we're going to handle inbound messages. So we've already got our path set up here today that actually handles that message. We're just going to move this down a little bit. So let's actually start doing something with this request. So first thing I need to do is, realign and reinvent that, grabbed the JSON of that body. So the messaging API does send them as JSON requests. I'm going to go ahead and de-steralize that using this API helper for the bandwidth callback message. All the inbound messages do come as a race, so we need to grab the zeroth element. There's only one element per array today. However, for type safety, we would recommend that you handle each request as if it could have more than one message. And then finally, we're going to grab the message content of that message. It looks a little something like this if we head to the messaging callbacks, inbound group message. You can see that there is a list. There is a single object within that list. And within that, there's an object called message that has the ID, the to and from, and an application ID. It's more of the content here, and then the type and time description are part of the overall callback. So it's a little bit of a distinguishment between the two. And then at this point, we're ready to move on. We need to check that callback direction. So as I've mentioned earlier, if the message or-- I don't know what I'm digging for there. I'll just realign. So if the message is an outbound DLR-- so as we've talked about before, there's only a single call back URL for outbound message request, as well as inbound message callbacks. So we do have a little check here. So message ID, here's the status. So we have that right there. And we can continue on now if we-- we're just going to return out of the function here just quick and dirty, go ahead and get out of the method, work on executing. Let's process stuff later. So now, we need to actually build the response that-- we'll go over what we're doing here. I'll just paste this. And again, realign our re-invent. So what we're going to do here, let's look at the message object that comes back in. So this is going to be built in a way to handle group message responses. So typically, it inbound message, generates one outbound message. If that message or that phone number just happened to be in a group message, we notice that the two array here, if the two field is now an array with multiple numbers in there. And one of those is actually the bandwidth number. So we have this 902 here. So what would happen if we were to simply grab the to and from? We would be missing out this 902 individual from the response to the group message. So we need to build a very quick array that represents the response object. So this little bit of Java code here is going to grab the phone numbers from the two array here. It's going to remove the owner, which would be this number here. It's going to remove 902. And then it's going to push back the from number. So in this point, we're building the response to include all the group members that were involved on the text, not just the individual that sent that text message. Otherwise, the individual on the phone that sent their original message would get a new thread from that bandwidth number without anyone else in the group. So now, let's keep going in our tutorial. We're going to start building a message request. So this is going to be what we're going to eventually send, oh, my, what we're going to eventually send to bandwidth to create that outbound message. So we know we need our application ID. It's defined earlier. We know we want to respond from the owner, the original recipient, the bandwidth number. And it's going to be to that list of numbers we just created. And we're going to build the incomplete message request. We don't have text, we don't have media. We're going to be rebuilding this request as we move further down. All right. Let's get a little bit of logic here. And I do have some dry violations. I have absolutely repeated myself within this code. And hopefully, that's forgivable for the purposes of demonstration. So first thing we're going to do, we're just going to take the text of that message. We're going to get rid of any white space, the surroundings. Because we don't want to do with the level of nuance. We're going to drop it to lower case, so we can let you spell dog with as many ups and caps as you need, and then we're going to check if it's equal to that. So if it's going to be a dog, we're going to rebuild-- first, we need to grab the media. So we're going to create a list of media. And this happens to be your URL here that I've put together for the demonstration. It's a dog picture, surprise. And then we're going to rebuild that request. We've got the text as a dog. We've got the media as this URL here, so it's going send in MMS. And we're going to build that. Next, we're going to go through send that message to bandwidth using the messaging controllers we defined earlier. So we've talked about how we have our controllers set up here. Notice now that it's not grayed out because we're actually using it. And we're going to print whatever that response is back to the console just so we can see. Finally, if there is an exception-- oops, sorry, we need to move up one. We're going to go ahead and catch and swallow any exceptions. Obviously, you would want to have much more robust error handling here. You would need to handle things like rate limit errors. You may even need to handle some failed phone number ownership. It just depends on what your situation is. You want to intelligently catch any exceptions where we're just catching them, logging them, and moving on. Not the recommended approach, but we're just talking about how to initially get started with the SDK, not a full-fledged best practices with bandwidth and Java. All right, so at this point, if it's a dog, we're going to send a dog message. If it's anything else we're just going to say hello from bandwidth. Relatively trivial, build that message, send it out, catch any errors, print them to the screen, and move on with our lives. And then finally, we're going to set the status to 200 just to explicitly say we received-- the bandwidth is responding back to-- or sorry, my server is responding back to the bandwidth callback saying 200 received. If, for whatever reason, the bandwidth callback is not acknowledged within the time out period, we're going to retry that request, and we'll retry for up to 24 hours backing off between these each-- backing off between each retry exponentially. So when your server does come back online, we're not going to overwhelm it with any missed requests. They'll trickle back in as over the duration of time. It's only within 24 hours. So at this point, we should have a very functioning application that is going to receive a text message. Check if it it's going to receive a callback, if the callback i with the delivery receipt, we're just going to log it to screen and return. Anything else, we're going to keep going on. We're going to grab the bandwidth number out of that callback request. We're going to use that information to build a response array so that we can respond to group messages and not just one single text messages. We're going to start building our message request. We're going to then start looking at the text of that request if it's a dog or that text message, if the text of the text message-- bleh, wordy. If it's a dog, we're going to send a dog pic and an emoji. Anything else, we're just going to say "Hello" from bandwidth and that's it. So let's see if it works. I've got my server running now. We're going to kill it and restart it Yep. And if I were to say, "Hi" to my bandwidth number, we can see there is, "Hello from bandwidth." So we can see what was logged to screen, right? It responded with this message here. So it says, "Hey, we're sending this message out." It then got the DLR letting us know that this message with the ID was received. Neat. Let's now ask for a dog. So same as before we did send that message. The message status [BEEP] has been delivered to a carrier and there's a super puppy with the dog emoji. So now we've built a nice little app that allows us to send and receive texts to and from a phone number or a group. So if you have a group text, same thing should work. We can see what's going on there. By-- We'll just put a little break point right here so we can inspect that numbers. Hi. [BEEP] And it does require that I'll launch this in debug mode. So let's actually do that. Here we go. And then now we can see our numbers is a list of one. And then I need to continue to go ahead and respond back to the bandwidth API. There we go. Cool. And just untick my break point. Let this run to just catch any extraneous text that may come in. So at this point it's time to move on to inbound calls. Inbound calls are driven using BXML. You can find information out about that by visiting the BXML section on the bandwidth website. Essentially any time you receive an inbound call we're going to create a callback request to the server as specified in your voice application. Whenever you receive that request we expect you to respond with something called BXML. It's a rich XML document. Excuse me. It's a rich XML document that's used to control a phone call programmatically. You can do things like play audio, you can speak a sentence, ask for digits, all sorts of stuff. Here's an example of what the raw XML would look like when you're building it in Java. Use the builder pattern as provided by the SDK. So let's actually dig into that. So at this point the tutorial shows us the final message handler, right? Complete the, draw the rest of the owl, so to speak. OK. Now, for voice handler. Here we are. Callbacks, inbound voice. So this is our entry point to the voice controller here. So the first thing we need to do is really consider what is this callback? This is always going to be initiated from an outbound-- from an inbound call and, in this case, we generally don't care about the phone number. We're always going to do the same thing for whatever inbound call it is. So the request that comes to us really isn't super important for this app. It does have things like the ID, the phone number, all the stuff about the phone call, but because we're always doing the same thing we really only needed to be alerted that there was an inbound call. And we're going to build a response-- Send that response back to bandwidth and then let that take place on the phone call. So we're actually going to be using the SpeakSentence and Gather verbs. So I'm just going to paste this into here and we'll realign everything. So let's take a look at what's going on here. I've created a SpeakSentence object that's going to be using our SpeakSentence builder and I'm going to set the text to just this quick arithmetic game. What's 9 plus 2? I'm going to be using Kate's voice to speak this and then I'm going to be asking people to respond to that. And in order to do that, we have to create a gather. Let's take a look at the documentation real quick. So I gather and we will actually pop in to the raw HTTP so we can see the XML here. So the gather is going to ask for a couple of things. It's going to ask for a gather URL. So this is the address. It can be relative or absolute. The address of the callback path that you want to receive events that happened about this gather to. Very wordy. Essentially, what URL do you want to be hit when the user presses one or two or five? So we're going to build a gather URL. We're going to be using the Nestable verbs for SpeakSentence so that are sentence request is within that Gather verb. So they're going to be one in the same actions. You can be interrupted in that SpeakSentence. If you know the answer so fast you can press it immediately. And then we're going to set the digits to two, right? We're not going to give you the opportunity to get it very wrong by putting in 1,000 for 9 plus 2, right? It'll only grab the first two digits that are pressed and it will give you 10 seconds to respond. Then, finally, we're going to build that response and then we're going to add the SpeakSentence and add the Gather. Which I do believe will actually speak the sentence and then gather. So we're going to do both in order. We're not actually going to nest the SpeakSentence within that. But if we wanted to, you can see the audio producer is the method that you would call on the-- there's our audio producer, which would take a SpeakSentence. And with the way we have it set up today, I don't want to break the demo. We'll just leave it like it is now. So at this point we finally need to return this BXML to the balance server to keep going on with everything. So now let's go here. Let's realign everything. And I'm going to drop a break point right here so that we can investigate what this BXML looks like, keeping in mind that I don't have my next path set up. Corrected all for gather response. We'll get to that in a second. Yeah. Stop and rerun. OK. So again, right, send dog. Get dog. And then let's actually call now. All right. I've hit my break point. So now you can see my BXML is going to be, let's play a game. [BUSY TONE] 9 plus 2. Going to hang this up because it's obviously not working because I never actually returned it. We were just looking at what happened here. So it's going to build this XML and send it back to bandwidth. However, we're not quite done yet. We need a hander-- handle this gather that response. Stop the execution. Pop into our tutorial. Got-- oh. We need to set the content type. Obviously this was not super required because it worked fine with just out it. It's just good record keeping to set the content type to what you're actually sending. Makes sense to me. And then finally, let's declare or gather handler. This is going to be our method for handling that gather request that comes in. Which when someone presses a button, they're going to be sent back to this path here. So this is going to receive the callback coming from bandwidth letting you know what buttons were pressed. So we're going to deserialize the callback. And, unfortunately, the voice SDK does not yet have the objects mapped to the gather requests generated so we need to use the generic JSON object. So we're just going to parse that out so we can grab some variables out of this callback object. And those are actually going to be the digits. And so-- look at what we have here real quick. So the way that you would access these digits is-- oh. Apologies. [CLICKING] There we are. Missed that. Yeah. There we go. So we're going to grab the digits from the JSON object, cast it to a string so that we're using the same thing, and I have a few URLs here. This is going to be our success wave and then our failure wave. So we're going to celebrate your success or sad-trombone your failure. And in order to create that URI without building a big if statement, right? If, get it wrong or right. We're just going to check real quick if the digits are equal to 11, set the media URL to the success file, if not send it to the fail file. We're going to build that BXML Just as we did before. So now we've got-- play audio is going to be using that URI. When we're done playing the audio, we just need to hang up the call. We're not doing anything more with it. We can just hang it up. Practice good call-- good call with bandwidth practices so to speak. And then finally, we're going to send this BXML as a response. Paste that in here. OK. So now if we take a look at what's going on complete through the voice, we're going to receive an inbound call. We're going to go ahead and just build some XML, some BXML, that says, "Hey, play a game." We're going to build a gather request. We're going to stitch those two together, send that back to the callee. They'll be presented with a quick gather request and press some digits. When they press those digits, it's going to come to our gather responder, or gather response handler, here. We're going to serialize or deserialize that into a JSON object. We have a couple of success and failure files based on the result of those digits. If they're 11, if they're one, one as a string, we're going to play success. If it's not, we're going to failure. We're going to build that BXML and respond to it. So, unfortunately, with the way that we're set up here today I don't have a great way of sending my call audio onto the mic. So what we'll do is we'll just do-- it's a debug so we're real live here. We'll do system.out.print line and we'll print the BXML to screen. Not a huge fan of this but it does work. And let's realign everything. Save it. And I believe we're ready to debug. OK. Let's kick it off. No breakpoints. Cool. All right. Text message. "Hi." Awesome. "Dog." Even better. Let's give it a call. PHONE: Play a game. What is 9 plus 2? INSTRUCTOR: All right. I'm going to dial 22. [SAD TROMBONE] As you can see, I got the failure there and the call was hung up. [DIAL TONE] Let's call back. PHONE: Play a game. What is 9 plus 2? [BEEP BEEP] [SUCCESS TONE] INSTRUCTOR: And I got my success. [DIAL TONE] All right. So thanks for hanging out with me today and going through a very rudimentary ad hoc demonstration of our bandwidth Java SDK and our dashboard. I would love to hear your questions and comments. Please reach out back to this email when you get it or email me directly at [email protected] will reach us. That again is [email protected] Looking forward to working with you all in the future. Cheers. Bye.