This article provides step by step instructions to develop a Java AGI based IVR (Interactive Voice Response) program on top of the Asterisk PBX system on the Ubuntu operating system. This also demonstrates how an IVR system can communicate with databases and REST web services.

This article can be useful for Java developers who want to set up their development environments for Asterisk-based AGI IVR programs.

Java AGI (Asterisk Gateway Interface) is an API used for extending the Asterisk functionalities (such as for IVR)using Java programming language.


For this demonstration, we are going to develop a simple stock order execution system using which users can buy stocks by calling an IVR system. Here’s the flow chart explaining the logic of the IVR,


This article uses the latest version of Asterisk and Java as of May 2020.

  • Ubuntu 18.04 OS
  • Asterisk 17
  • JDK version “14.0.1”
  • Maven
  • MySQL Database
  • MicroSIP Softphone (

Note: Please make sure full internet access is available in the Ubuntu server.


Download “jdk-14.0.1_linux-x64_bin.tar.gz” file from

Login to the Ubuntu Linux server as a root user.

FTP “jdk-14.0.1_linux-x64_bin.tar.gz” into /tmp directory. Extract the file using the following commands,

Extract the file using the following commands,

cd /tmp
tar -xvf jdk-14.0.1_linux-x64_bin.tar.gz

Move the extracted files to /opt dir,

mv jdk-14.0.1 /opt/

Open the “~/.bashrc” file to setup environment variables,

nano ~/.bashrc

Add the following lines at the end of the document,

export JAVA_HOME=/opt/jdk-14.0.1
export PATH=${PATH}:${JAVA_HOME}/bin

Save and exit the file.

Execute the following command to load the environment variables,

source ~/.bashrc

Execute the following commands to make sure the environment variables are set properly,

echo $PATH

Execute the following command to make sure java is installed properly. This command will print the java version information,

java -version


Execute these commands to install Maven.

apt-get update
apt-get -y install maven

Verify the installation by,

mvn --version


Login to the Ubuntu Linux server as a root user.

Execute this command to install MySQL,

apt-get update
apt-get -y install mysql-server

Secure the MySQL DB by,



Enter root password

Press Y for the rest of the options.

Login into MySQL as the root user,

mysql -u root -p

Execute the following queries to create a database called “stock_db” and a user called “stock_db_user”,


GRANT ALL ON stock_db.* TO 'stock_db_user'@'localhost' IDENTIFIED BY '<PASSWORD>';


Create tables and sample data with commands below,

use stock_db;

create table customer(id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, password INT NOT NULL, PRIMARY KEY ( id ));

insert into customer(user_id, password) values(100, 123456);

create table stock_order(id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, stock_symbol VARCHAR(10) NOT NULL, qty INT NOT NULL, price FLOAT(8,2), PRIMARY KEY ( id ));



  1. Login into Ubuntu as the root user
  2. Download and extract the Asterisk zip by executing the following commands,
cd /usr/src/


tar zxf asterisk-17-current.tar.gz

cd asterisk-17.*/

3. Execute the following commands to install the asterisk prerequisites,

apt-get update

apt-get install build-essential wget libssl-dev libncurses5-dev libnewt-dev libxml2-dev linux-headers-$(uname -r) libsqlite3-dev uuid-dev git subversion

git clone git:// pjproject

cd pjproject

./configure --prefix=/usr --enable-shared --disable-sound --disable-resample --disable-video --disable-opencore-amr CFLAGS='-O2 -DNDEBUG'

make dep

make && make install


ldconfig -p |grep pj

cd /usr/src/asterisk-17.*/

sh /usr/src/asterisk-17.*/contrib/scripts/

/usr/src/asterisk-17.*/contrib/scripts/install_prereq install

The above command will prompt you for the ITU-T code. It’s nothing but a country telephone code. (For example 1 for the United States, 61 for Australia, 33 for France)

4. Execute the following commands to verify the prerequisites,

cd /usr/src/asterisk-17.*/


5. Execute the following command to select the asterisk modules,

cd /usr/src/asterisk-17.*/

make menuselect

6. Select the format_mp3 as shown below,

7. Press the F12 key to save and exit.

8. Compile the Asterisk by below command,(This command may take several minutes)


9. Install Asterisk by below command,

make install

10. Execute the following commands to create configuration,

make samples

make basic-pbx

make config


11. Create start script for Asterisk,

cp /usr/src/asterisk-17.*/contrib/init.d/rc.debian.asterisk /etc/init.d/asterisk

12. Open the file /etc/init.d/asterisk and configure the properties as mentioned below,


13. Execute the command below,

systemctl daemon-reload

14. Test the start/stop of Asterisk service with below commands,

/etc/init.d/asterisk start

/etc/init.d/asterisk stop


NOTE: Please make sure the port 5060 is open in the Ubuntu server so that softphones can communicate with Asterisk.

  1. To add a phone number(for example x 100), open the /etc/asterisk/pjsip.conf file, add the following lines at the end of the file.



2. Save and exit the /etc/asterisk/pjsip.conf file.

3. Restart the Asterisk service,

/etc/init.d/asterisk stop	
/etc/init.d/asterisk start


  1. Create a new directory to hold the program files,
mkdir /etc/asterisk/java_agi

2. Download the demo project (The project is publicly available in GitHub)

cd /etc/asterisk/java_agi


apt -y install unzip

unzip /etc/asterisk/java_agi/

3. The project structure will look like the following,

4. Build the project,

cd /etc/asterisk/java_agi/stock_order_ivr_system_demo-master

mvn install

5. Please make sure the build is successful as shown below,

6. Copy the audio files required to asterisk directory,

cp /etc/asterisk/java_agi/stock_order_ivr_system_demo-master/audio_files/*.gsm /var/lib/asterisk/sounds/en/

7. Start the Java AGI program,

cd /etc/asterisk/java_agi/stock_order_ivr_system_demo-master/target

$JAVA_HOME/bin/java -jar STOCK_ORDER_IVR.jar &


Here are the source files implementing the IVR logic. Please read the comments inside the source code to understand the implementation.

The service() method of the StockOrderIVR class will be invoked whenever the user calls the extension(9999). This is the main class implementing the IVR logic and it uses other helper classes.

This IVR implementation will cover most of the common scenarios such as,

  • Retry option in case of an invalid entry
  • Confirmation of entry by the user
  • Database lookup
  • Web Service lookup
  • Responding to the user with the transaction id.

You can refer to the Asterisk AGI API Documentation ( to read more about the AGI methods used in this class.

package com.thedeveloperfriend.asterisk.ivrdemo;

import org.asteriskjava.fastagi.AgiChannel;
import org.asteriskjava.fastagi.AgiException;
import org.asteriskjava.fastagi.AgiRequest;
import org.asteriskjava.fastagi.BaseAgiScript;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

//Asterisk AGI class implementing demo stock order IVR system
public class StockOrderIVR extends BaseAgiScript {
	Logger logger = LogManager.getRootLogger();
	DBHelper dbHelper = new DBHelper();
	RestWebServiceHelper restWebServiceHelper = new RestWebServiceHelper();

	// This method will be invoked whenever a user calls the extension 9999
	public void service(AgiRequest request, AgiChannel channel) throws AgiException {
		try {
			// Answer the incoming call
			this.logger.debug("CALL ANSWERED ......");

			// Get the caller number. This is the User Id
			String userId = request.getCallerId();
			this.logger.debug("userId = " + userId);

			// Get 6 digit password from the user and validate against database

			// Ask the user to select a stock by pressing 1,2 or 3
			String selectedStockSymbol = getUserSelectedStockSymbol();
			this.logger.debug("userId = " + userId + " : selectedStockSymbol = " + selectedStockSymbol);

			// get the price of the selected stock by calling a REST web service.
			double stockPrice = restWebServiceHelper.getStockPrice(selectedStockSymbol);
			this.logger.debug("userId = " + userId + " : stockPrice = " + stockPrice);

			// get the quantity from user
			String quantity = getQuantity();
			this.logger.debug("userId = " + userId + " : quantity = " + quantity);

			// confirm the quantity with user
			if (confirmQuantity(quantity)) {
				this.logger.debug("userId = " + userId + " : User confirmed the quantity");
				// user confirmed the quantity so go ahead and place the order
				long orderId = dbHelper.insertOrderDetails(Integer.parseInt(userId), selectedStockSymbol,
						Integer.parseInt(quantity), stockPrice);
				// Play the audio "You have successfully placed the order. Your Order Id is"
				// Play the Order Id generated

			} else {
				// user did't confirm the quantity

			// Terminate the call
			this.logger.debug("CALL HANGUP ......");
		} catch (UserReTryCountExceededException e) {
			// Stream the audio for user exceeded all the retry options

			// Terminate the call since the user exceeded all the retry options
			this.logger.debug("CALL HANGUP ......");

		} catch (Exception exception) {


	// This method will receive and validate the six-digit password from the user.
	// If password is empty or invalid it will prompt the user to reenter for
	// maximum 3 times
	public void getAndValidatePassword(String user_id) throws AgiException, UserReTryCountExceededException, Exception {
		String password = null;
		try {
			int retry_counter = 0;
			while (true) {
				// Play the audio "Please enter your six digit password"
				password = getData("ask_password", 15000, 6);
				this.logger.debug("password = " + password);

				if (password != null && password.length() == 6 && StringUtils.isNumeric(password)
						&& dbHelper.validateUsernamePassword(user_id, password)) {
				} else if ((retry_counter < 2)) {
					// Play the audio "You have entered an invalid option"
				} else {
					throw new UserReTryCountExceededException("User exceeded all retry options");


		} catch (UserReTryCountExceededException e) {
			throw e;
		} catch (Exception exception) {
			throw exception;


	// Method to ask the user to select his stock.
	// 1 for IBM, 2 for MSFT or 3 for ORCL
	public String getUserSelectedStockSymbol() throws UserReTryCountExceededException, AgiException {

		try {
			int retry_counter = 0;
			while (true) {
				// Play the audio "Please select your stock. Press 1 for IBM. Press 2 for
				// MicroSoft. or Press 3 for Oracle"
				String selectedOption = getData("select_stock", 15000, 1);
				this.logger.debug("selectedOption = " + selectedOption);

				// make sure the selectedOption is not null
				if (selectedOption == null) {
					selectedOption = "";

				switch (selectedOption) {
				case "1":
					return "IBM";
				case "2":
					return "MSFT";
				case "3":
					return "ORCL";
					// Play the audio "You have entered an invalid option"
					if ((retry_counter < 2)) {
					} else {
						throw new UserReTryCountExceededException("User exceeded all retry options");



		} catch (UserReTryCountExceededException e) {
			throw e;

	// Method to ask the user to enter the quantity followed by hash symbol
	// The maximum digits allowed is 4
	public String getQuantity() throws AgiException, UserReTryCountExceededException {

		try {
			int retry_counter = 0;
			while (true) {
				// Play the audio "Please enter the buy quantity"
				String qty = getData("enter_quantity", 15000, 4);
				this.logger.debug("quantity typed by user = " + qty);

				if (qty != null && StringUtils.isNumeric(qty)) {
					return qty;
				} else if ((retry_counter < 2)) {
					// Play the audio "You have entered an invalid option"
				} else {
					throw new UserReTryCountExceededException("User exceeded all retry options");


		} catch (UserReTryCountExceededException e) {
			throw e;


	// Method to confirm the quantity number entered
	public boolean confirmQuantity(String quantity) throws AgiException {
		boolean isCorrectQuantity = false;
		// Play the quantity to user

		// Press 1 to confirm. Press 2 for cancel
		String confirmationCode = getData("ask_confirmation", 15000, 1);

		if (confirmationCode != null && confirmationCode.equals("1")) {
			isCorrectQuantity = true;

		return isCorrectQuantity;


The class implementing the database access methods.

package com.thedeveloperfriend.asterisk.ivrdemo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

//Database helper class for IVR program
public class DBHelper {

	Logger logger = LogManager.getRootLogger();

	private static String HOST_NAME = "localhost";
	private static String PORT = "3306";
	private static String DATABASENAME = "stock_db";
	private static String USERNAME = "stock_db_user";
	private static String PASSWORD = "Password@123";

	private Connection getConnection() throws ClassNotFoundException, SQLException {
		Connection connection = null;
		connection = DriverManager.getConnection("jdbc:mysql://" + HOST_NAME + ":" + PORT + "/" + DATABASENAME,
		return connection;

	private void closeDBObjects(ResultSet resultSet, PreparedStatement preparedStatement, Connection connection)
			throws SQLException {

	public boolean validateUsernamePassword(String user_id, String password)
			throws ClassNotFoundException, SQLException {
		boolean isValidCredentials = false;
		Connection connection = getConnection();
		String sqlQuery = "select * from customer where user_id=? and password=?";
		PreparedStatement preparedStatement = connection.prepareStatement(sqlQuery);
		preparedStatement.setString(1, user_id);
		preparedStatement.setString(2, password);
		ResultSet resultSet = preparedStatement.executeQuery();
		if ( {
			isValidCredentials = true;

		closeDBObjects(resultSet, preparedStatement, connection);
				"DBHelper:validateUsernamePassword:user_id=" + user_id + ":isValidCredentials=" + isValidCredentials);
		return isValidCredentials;

	public long insertOrderDetails(int user_id, String stock_symbol, int qty, double price)
			throws SQLException, ClassNotFoundException {
		long generatedOrderId = 0;
		Connection connection = getConnection();
		String sqlQuery = "insert into stock_order (user_id, stock_symbol, qty, price) values(?,?,?,?)";
		PreparedStatement preparedStatement = connection.prepareStatement(sqlQuery,
		preparedStatement.setInt(1, user_id);
		preparedStatement.setString(2, stock_symbol);
		preparedStatement.setInt(3, qty);
		preparedStatement.setDouble(4, price);

		ResultSet resultSet = preparedStatement.getGeneratedKeys();
		if ( {
			generatedOrderId = resultSet.getLong(1);

		return generatedOrderId;


This class has a method to call the publicly available REST Web Service,

package com.thedeveloperfriend.asterisk.ivrdemo;

import org.json.JSONObject;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

//Helper class to call REST web services
public class RestWebServiceHelper {

	// get the stock price by calling a online demo REST web service
	// (
	public double getStockPrice(String stockSymbol) {

		Client client = Client.create();
		WebResource webResource = client.resource(
				"" + stockSymbol);
		ClientResponse response = webResource.accept("application/json").get(ClientResponse.class);

		String output = response.getEntity(String.class);
		JSONObject jsonObject = new JSONObject(output);
		String symbol = jsonObject.getString("StockSymbol");
		double price = jsonObject.getDouble("Price");

		return price;



This is a custom exception class created to handle whenever a user exceeds the number of retry attempts.

package com.thedeveloperfriend.asterisk.ivrdemo;

//This is a custom exception class created to handle scenarios where a user exceeds the maximum number of retry counts when providing some inputs 
public class UserReTryCountExceededException extends Exception {

	private String errorMessage;

	public UserReTryCountExceededException(String errorMessage) {
		this.errorMessage = errorMessage;

	public String toString() {
		return errorMessage;


Here’s the Maven pom.xml which has the references for all the dependencies such as fastagi, MySQL, etc.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=""
					<excludes />

Configuring & Mapping an Extension to Java AGI Server

  1. Open “/etc/asterisk/modules.conf” and the following lines.
load =
load =
load =

2. Execute the following command to open the  asterisk command line,

asterisk -rvvvvvv

3. Execute the following commands in asterisk CLI,

module load

module load

module load


4. Open the /etc/asterisk/extensions.conf file and add these lines at the end of the file,

exten = 9999,1,AGI(agi://localhost/stockorder.agi)

5. Restart the Asterisk Server,

/etc/init.d/asterisk stop
/etc/init.d/asterisk start


The IVR debug logs will be generated in the file “/var/log/asterisk/stock_order_ivr.log”.


NOTE: Please make sure the port 5060 is open in the Ubuntu server so that softphones can communicate with Asterisk.

  1. Download and install MicroSIP softphone from in a Windows 10 machine. It’s an open-source softphone.

2. Open MicroSIP from the start menu

3. Go to Add Account Option and enter details as shown below,

SIP Server = IP Address of Asterisk server

Username = 100 (as configured in pjsip.conf above)

Password = 12345678 (as configured in pjsip.conf above)

Domain = IP Address of Asterisk server

4. Make sure the softphone is online.

5. Dial 9999 to test the IVR program.


All the source code/contents discussed here is available in public GitHub,


While developing the IVR you may want to create some audio files quickly for testing. You can use the following online tools to create the same.

Visit to convert text to audio (.wav file). For example,

Download the .wav file created.

Visit to convert .wav file to .gsm file. For example,

Now you can upload the .gsm file into /var/lib/asterisk/sounds/en directory.

Follow Me


  1. Hi! Good Job! I have one question: When you compile this project you generate a jar file ? If so, how you can run with java command ?
    This jar has all dependency added ?
    java asterisk-java myagiserver this would be the way for running ?
    java asterisk-java myagiserver ??

    1. Please have a look at the section “DEVELOPING AGI JAVA PROGRAM” in this article. Here we are building the project using maven. So it will take care of the dependencies. Step number 7 of this section tells how to start the JAR file. Thanks

      1. Thks very much I can solved the problem and everything went fine but when I run the server I get this error:
        java -jar STOCK_ORDER_IVR.jar
        Error: Could not find or load main class org.asteriskjava.fastagi.DefaultAgiServer
        Caused by: java.lang.ClassNotFoundException: org.asteriskjava.fastagi.DefaultAgiServer

        1. Please make sure the “dependency-jars” folder exists with “asterisk-java-1.0.0-final.jar” in the same folder of STOCK_ORDER_IVR.jar file. This is generated by maven. So if you are copying STOCK_ORDER_IVR.jar to another location then please copy along with the “dependency-jars” folder

          1. You are rigth again!! I did NOT realize that the maven configuration was arranged to have the dependencies outside the jar file.

            Thks so much, again!

  2. Hi!
    I did your example but I have a question some weird. I did other class to do the get information from phone just for getting, this new class is instanced and send in the constructor AgiRequest request, AgiChannel channel to this new instance the problem is when I do this
    channel.getData(prompt,15000,1) never stop to get data, I press any number in the phone but never read the data just play the message but never read but if I do this like you, in the main class, everything is fine, really I dont know.
    There is a reason why when I pass the channel and request to a new class this doesnt work ? Maybe there is a behavior that I dont understand
    Meanwhile, I must to do using just one class to get information, but it is so weird.

    Thks In advance

    1. Hi Edwin,
      I think the possible reason is,
      If you notice, I am calling the getData method from BaseAgiScript class which is inherited by the StockOrderIVR class.
      But you are trying to call the getData method from AgiChannel.
      So I think you need to pass the reference of the main class (StockOrderIVR) to your new class. And call getData using this reference.
      Hope this helps. Please let me know

      1. Thks! I am gonna try that , I think that is the reason too. To pass a reference of the main class would be to create an object from this and pass to another class/?

        Thks for your help 😉

  3. Hi. Excellent job.
    I have only one question, it’s necessary to create a different thread for each phone calling? or it’s asterisk-java who creates a separate thread for each one?

    1. Hi, Good Question. Yes, it’s taken care of by asterisk-java. Only one instance of the StockOrderIVR class will exist, and for each AGI request(call to extension) system will create a separate thread of service() method. So we don’t have to take care of creating threads. This is similar to Java Servlets, where the web container will create a separate thread for serving each HTTP request. Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *