Of course! Here is a comprehensive guide to using Java Sockets in Android, covering the basics, a practical example (a simple chat app), and crucial considerations for the Android platform.

Understanding the Basics: TCP vs. UDP
When we talk about "Sockets" in networking, we're usually referring to one of two protocols:
-
TCP (Transmission Control Protocol):
- Analogy: A phone call. You establish a connection, talk, and then hang up.
- Characteristics: Connection-oriented, reliable, ordered, and error-checked. It's great for when you need to ensure all data arrives correctly (e.g., file transfer, web browsing, chat messages).
- Classes in Java:
Socket(client),ServerSocket(server).
-
UDP (User Datagram Protocol):
- Analogy: Sending a postcard. You send it, but you don't know if or when it will arrive.
- Characteristics: Connectionless, fast, but unreliable and unordered. It's great for real-time applications where speed is more important than perfect accuracy (e.g., video streaming, online gaming, VoIP).
- Classes in Java:
DatagramSocket,DatagramPacket.
For this guide, we'll focus on TCP, as it's the most common starting point.

Key Considerations for Android
Using sockets on Android has some unique requirements compared to a standard Java application.
a. Network Permissions
Your app must explicitly declare that it needs internet access. Add this to your AndroidManifest.xml:
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application ...>
...
</application>
</manifest>
b. Performing Network Operations on a Background Thread
This is the most critical rule. You cannot perform network operations (like opening a socket or reading/writing data) on the main UI thread. Doing so will result in a NetworkOnMainThreadException and will likely make your app unresponsive.
The standard way to handle this is with an AsyncTask, Thread, Handler, or, more modernly, a Coroutine (if using Kotlin) or ExecutorService (in Java).

c. Android's Battery Optimization (Doze Mode)
Since Android 6.0 (Marshmallow), the OS aggressively tries to save battery. In "Doze" mode, network access is severely restricted. For a persistent socket connection (like a chat app), you need to handle this.
- Foreground Service: If your app needs to maintain a socket connection while in the background, you should run it as a Foreground Service. This makes the service a high-priority task and prevents the system from killing it during Doze mode. A Foreground Service must show a persistent notification.
Practical Example: Simple Client-Server Chat App
Let's build a simple chat application. We'll have a server that echoes back any message it receives and a client that can send messages and display received ones.
Part 1: The Server (Standard Java Application)
The server is best run on a regular computer or a cloud server (like AWS, Google Cloud, or Heroku). It's not an Android app.
Server.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
private static final int PORT = 12345;
private static List<ClientHandler> clients = new ArrayList<>();
private static ExecutorService pool = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server is listening on port " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
ClientHandler clientThread = new ClientHandler(clientSocket);
clients.add(clientThread);
pool.execute(clientThread);
}
}
}
// This inner class handles communication with a single client
static class ClientHandler implements Runnable {
private Socket clientSocket;
private PrintWriter out;
private BufferedReader in;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try {
out = new PrintWriter(clientSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Received from client: " + inputLine);
// Echo the message back to the client
out.println("Server: " + inputLine);
// Broadcast to all other clients (optional for a real chat app)
// for (ClientHandler client : clients) {
// if (client != this) {
// client.out.println("Client says: " + inputLine);
// }
// }
}
} catch (IOException e) {
System.out.println("Error handling client: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
clients.remove(this);
}
}
}
}
Part 2: The Android Client (App)
This will be a simple Android app with an EditText to type messages and a TextView to display the chat log.
Step 1: Layout (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status: Disconnected"
android:textSize="18sp"
android:textStyle="bold"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:id="@+id/chatTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Chat will appear here..."
android:textSize="16sp"/>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/messageEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type a message"/>
<Button
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send" />
</LinearLayout>
</LinearLayout>
Step 2: The Java Code (MainActivity.java)
We'll use an ExecutorService to manage our background thread.
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private TextView chatTextView, statusTextView;
private EditText messageEditText;
private Button sendButton;
// Network components
private Socket socket;
private PrintWriter out;
private BufferedReader in;
// Threading
private ExecutorService executorService;
private Handler mainHandler; // To update UI from the background thread
// Server details (replace with your server's IP address)
private static final String HOST = "10.0.2.2"; // Special IP for localhost from emulator
// private static final String HOST = "YOUR_SERVER_IP"; // For a real device
private static final int PORT = 12345;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
chatTextView = findViewById(R.id.chatTextView);
statusTextView = findViewById(R.id.statusTextView);
messageEditText = findViewById(R.id.messageEditText);
sendButton = findViewById(R.id.sendButton);
executorService = Executors.newSingleThreadExecutor();
mainHandler = new Handler(Looper.getMainLooper());
sendButton.setOnClickListener(v -> {
String message = messageEditText.getText().toString();
if (!message.isEmpty()) {
sendMessage(message);
messageEditText.setText("");
}
});
// Connect to the server in the background
executorService.execute(this::connectToServer);
}
private void connectToServer() {
try {
statusTextView.setText("Connecting..."); 