Saturday, February 12, 2022

Java Tutorial: Exploring java.net.http Package

Chapters

Overview

java.net.http package provides high-level client interfaces to HTTP (versions 1.1 and 2) and low-level client interfaces to WebSocket. This package has classes that can be alternatives to URLConnection, HttpURLConnection and HttpsURLConnection.

I discussed HttpURLConnection and HttpsURLConnection in this article. One of the advantages of this package over URLConnection is that this package can asynchronously handle HTTP requests and responses.

HttpRequest, HttpClient and HttpResponse

HttpRequest is used to create a request that is sent to a server. HttpClient is used to send request to a server and get server's response to the request. HttpResponse holds response that has been sent by server to a client.

First off, we need to create a HttpRequest and HttpClient. HttpResponse can't be created directly. It's created once HttpClient retrieves server's response. To create a HttpRequest and HttpClient, we need to created builders by invoking newBuilder() method. Once we're done building them, we invoke their build() methods.

If you wanna try the example below, you need a web server like apache web server. You can install XAMPP to easily install apache web server. Once you have apache web server, start the server, create a directory and name it as "test" in your document root and create a php file there. Name the php file as "url_con_test_get.php". Then, put these codes in the php file:
<?php
echo str_shuffle($_GET['text']);
?>
This example demonstrates HttpRequest, HttpClient and HttpResponse.
import java.net.http.*;
import java.net.*;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    //Request
    HttpRequest request = HttpRequest.
    newBuilder(new URI("http://localhost/"+
    "test/url_con_test_get.php?text=myText")).
    version(HttpClient.Version.HTTP_2).
    headers("Content-Type", 
            "text/plain;charset=UTF-8").
    GET().
    build();
    
    //Request-response handler
    HttpClient handler = HttpClient.
    newBuilder().build();
    
    HttpResponse<String> response = 
    handler.send
    (request, 
     HttpResponse.BodyHandlers.ofString());
    System.out.println("response: " + response.body());
  }
}

Result(may vary)
response: temyxT
First off, to create a HttpRequest, we invoke HttpRequest.newBuilder method. This method returns HttpRequest.Builder instance. newBuilder has three forms:

newBuilder() - Creates an HttpRequest builder.
newBuilder(HttpRequest request, BiPredicate<String,String> filter) - Creates a Builder whose initial state is copied from an existing HttpRequest.
newBuilder(URI uri) - Creates an HttpRequest builder with the given URI.


In the example above, I used the third form of newBuilder method. version method in HttpRequest.Builder sets the preferred HttpClient.Version for this request. This method returns HttpRequest.Builder with the specified HttpClient.Version. Take note that if the specified version is HttpClient.Version.HTTP_2 and HTTP version 2 is not supported, the specified version will rollback to HTTP version 1.1. Adding version in HttpRequest is optional.

headers method in HttpRequest.Builder adds the given key-value pairs to the set of headers for the request. HTTP header consists of a key-value pair. key denotes header name and value denotes the value of the header. headers method has another variant which is the header method. header method only accepts a single key-value pair. Although, it can be invoked multiple times. Adding headers in HttpRequest is optional.

GET method Sets the request method of builder to GET. build() builds an HttpRequest with the specified version, headers, request method, etc. Just a reminder, a GET query with a single value looks like this: ?name=value
a GET query with multiple values looks like this: ?name1=value1&name2=value2
Request method is optional.

Next, to create a HttpClient, we invoke HttpClient.newBuilder method. This method returns HttpClient.Builder instance. If we just want to create a HttpClient with default settings, we can invoke HttpClient.newHttpClient method. This syntax HttpClient.newBuilder().build() is equivalent to invoking HttpClient.newHttpClient.

Next, once we have created a HttpRequest and HttpClient, we can invoke send method. This method requires HttpRequest and HttpResponse.BodyHandler arguments. HttpResponse.BodyHandler<T> is a handler for response bodies.

The class HttpResponse.BodyHandlers provides implementations of many common body handlers. HttpResponse.BodyHandlers.ofString() method indicatees that the response body is going to be String type.

send method has another variant which is the sendAsync method. sendAsync method is used for asynchronous request-response tasks. send method is used for synchronous request-response task.

Next, once send method returns a HttpResponse, we can get the response body by invoking the body() method of HttpResponse.

Next, let's change the request method to POST. If you wanna try the example below, you need a web server like apache web server. You can install XAMPP to easily install apache web server. Once you have apache web server, start the server, create a directory and name it as "test" in your document root and create a php file there. Name the php file as "url_con_test_post.php". Then, put these codes in the php file:
<?php
echo str_shuffle($_POST['text']);
?>
This example demonstrates POST request method.
import java.net.http.*;
import java.net.*;
import java.io.IOException;
import java.time.Duration;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    //Request
    HttpRequest request = HttpRequest.
    newBuilder(new URI("http://localhost/"+
    "test/url_con_test_post.php")).
    timeout(Duration.ofSeconds(5)).
    version(HttpClient.Version.HTTP_2).
    headers("Content-Type", 
            "application/x-www-form-urlencoded").
    //or "text1=String1&text2=String2"
    //for multiple key-value pairs
    POST(HttpRequest.BodyPublishers.
         ofString("text=String")).
    build();
    
    //Request-response handler
    HttpClient handler = HttpClient.
    newBuilder().build();
    
    HttpResponse<String> response = 
    handler.send
    (request, 
     HttpResponse.BodyHandlers.ofString());
    System.out.println("response: " + response.body());
  }
}

Result(may vary)
ngSrti
application/x-www-form-urlencoded is used to handle POST data. There's another Content-Type value that handles POST data and that's the multipart/form-data. I think this discussion has a good explanation regarding the difference of these two Content-Type values.

timeout method sets a timeout for this request. If the response is not received within the specified timeout then an HttpTimeoutException is thrown from HttpClient::send or HttpClient::sendAsync completes exceptionally with an HttpTimeoutException. Setting timeout is optional.

HttpRequest.BodyPublishers contains implementations of BodyPublisher that implement various useful publishers, such as publishing the request body from a String, or from a file. Request body is not limited to String, we can create a request body for bytes, input stream or even a file. You can read the examples about request body types in the documentation.

Handling Redirects

Handling redirects here is similar to handling redirects in HttpURLConnection. You may wanna look at this article regarding handling redirects in HttpURLConnection.

This example demonstrates handling redirects manually.
import java.net.http.*;
import java.net.*;
import java.io.IOException;
import java.time.Duration;
import java.util.Optional;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    URI uri = URI.create("http://www.twitter.com");
    boolean checkRedirect = true;
    
    HttpRequest request = null;
    HttpClient handler = null;
    HttpResponse<Void> response = null;
    while(checkRedirect){
      //Request
      request = HttpRequest.
      newBuilder(uri).
      timeout(Duration.ofSeconds(5)).
      build();
      
      //HttpClient
      handler = HttpClient.
      newBuilder().
      followRedirects(HttpClient.Redirect.NEVER).
      build();
      
      response = handler.send(request,
      HttpResponse.BodyHandlers.discarding());
      
      if(response.statusCode() == 
         HttpURLConnection.HTTP_MOVED_PERM ||
         response.statusCode() ==
         HttpURLConnection.HTTP_MOVED_TEMP ||
         response.statusCode() == 
         HttpURLConnection.HTTP_SEE_OTHER){
         
         Optional<String> redirect =
         response.headers().firstValue("Location");
         System.out.println
         ("Redirect URL: " + redirect.get());
         uri = URI.create(redirect.get());
         
      }else checkRedirect = false;
    } 
  }
}

Result
Redirect URL: https://www.twitter.com
Redirect URL: https://twitter.com
followRedirects in HttpClient.Builder specifies whether requests will automatically follow redirects issued by the server. HttpClient has three policies regarding following redirects and they can be found in HttpClient.Redirect enum.

HttpClient.Redirect.NEVER means that HttpClient will never automatically follow redirects. If you wanna automatically follow redirects you can use HttpClient.Redirect.ALWAYS. Careful thougn, Following redirects can be dangerous.

HttpResponse.BodyHandlers.discarding method returns a response body handler that discards the response body. Since we don't need response body in the example above, it's alright to discard it. headers method in HttpResponse returns the response headers. This method return a HttpHeaders instance.

firstValue method returns the first value of a header. Some HTTP headers can have multiple values. Location header only has one value which is the redirect URL.

Sending HTTP Requests Asynchronously

If you wanna try the example below, you need a web server like apache web server. You can install XAMPP to easily install apache web server. Once you have apache web server, start the server, create a directory and name it as "test" in your document root and create a php file there. Name the php file as "url_con_test_get.php". Then, put these codes in the php file:
<?php
echo str_shuffle($_GET['text']);
?>
To send requests asynchronously, we will use the sendAsync method from HttpClient. This example demonstrates sendAsync method.
import java.net.http.*;
import java.net.*;
import java.io.IOException;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    int ln = 2;
    
    URI[] uris = new URI[ln];
    
    uris[0] = URI.create("http://localhost/"+
    "test/url_con_test_get.php?text=String1");
    uris[1] = URI.create("http://localhost/"+
    "test/url_con_test_get.php?text=String2");
    
    HttpRequest[] requests = new HttpRequest[ln];
    
    //Request-response handler
    HttpClient handler = HttpClient.
    newHttpClient();
    
    ArrayList
    <CompletableFuture<HttpResponse
    <String>>> responses =
    new ArrayList<>();
    
    //requests
    for(int i = 0; i < ln; i++){
      requests[i] = 
      HttpRequest.newBuilder(uris[i]).
      headers("Content-Type", 
            "text/plain;charset=UTF-8").
      GET().build();
    }
    
    //responses
    for(int i = 0; i < ln; i++){
      responses.add
      (handler.sendAsync(requests[i],
      HttpResponse.BodyHandlers.ofString()));
    }
    
    for(
    CompletableFuture
    <HttpResponse<String>> r : responses)
      System.out.println(r.get().body());
  }
}

Result(may vary)
irn1tSg
rSgin2t
If you're not familiar with CompletableFuture class, I explained the class in this article. If you wanna implement your own executor, invoke executor method of HttpClient.Builder. For example:
ExecutorService myExecutor...
...
HttpClient handler = HttpClient.
newBuilder().
executor(myExecutor).
build();
The default executor that HttpClient uses is java.util.concurrent.Executors.newCachedThreadPool(). Also, Take note that Flow API has been implemented to HttpRequest, HttpClient and HttpResponse. HttpRequest has BodyPublisher whereas HttpResponse has BodySubsriber. HttpClient is acting like Flow.processor. I explained Flow API in article.

Java provides HttpRequest.BodyPublishers and HttpResponse.BodySubscribers that contain useful implementations of BodyPublisher and BodySubscriber. Although, you can create your own implementations if the implementations that java provide don't suffice.

Handling Cookies

To handle cookies, first off, we need to create a CookieManager. Then, invoke cookieHandler method of HttpClient and put your cookie manager there as argument. You should read this article regarding handling of cookies using URLConnection because I'm gonna implement a similar approach to this example.
import java.net.http.*;
import java.net.*;
import java.time.Duration;
import java.util.List;

public class SampleClass{
  
  public static void main(String[] args)
                        throws Exception{
    URI uri = URI.create("https://www.google.com");
    
    HttpRequest request = 
    HttpRequest.newBuilder(uri).
    timeout(Duration.ofSeconds(5)).
    build();
    
    HttpClient handler =
    HttpClient.newBuilder().
    cookieHandler(new CookieManager()).
    build();
    
    HttpResponse response = handler.send(request,
    HttpResponse.BodyHandlers.discarding());
    
    System.out.println("Response Status Code: "
    + response.statusCode());
    System.out.println();
    
    CookieManager cm = 
    (CookieManager)handler.
    cookieHandler().get();
    
    CookieStore store = cm.getCookieStore();
    List<HttpCookie> cookieList =
    store.getCookies();
    
    for(HttpCookie cookie : cookieList){
      System.out.println
      ("Domain: " + cookie.getDomain());
      System.out.println
      ("Cookie Name: " + cookie.getName());
      System.out.println
      ("Max Age: " + cookie.getMaxAge());
      System.out.println();
    }
    
  }
}

Result(may vary)
Response Status Code: 200

Domain: .google.com
Cookie Name: 1P_JAR
Max Age: 2592003

Domain: .google.com
Cookie Name: NID
Max Age: 15811203
cookieHander method returns an Optional<CookieHandler> instance. The CookieHandler in the Optional instance is the CookieManager that we assigned in cookieHandler of HttpClient.Builder.

HTTP/2 Push Promise

Push Promise is a new feature in HTTP/2(HTTP version 2) that allows server to push resources to the browser without making clients request for them. Push promise increases performance such as page loading. Java supports handling Push Promises via sendAsync method. I think this article has a good explanation regarding push promise. You should read it.

This example demonstrates handling push promise.
import java.net.http.*;
import java.net.*;
import java.time.Duration;
import java.util.function.Function;
import java.util.concurrent.CompletableFuture;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    URI uri = 
    URI.create("https://http2.golang.org/serverpush");
  
    HttpRequest pageRequest = 
    HttpRequest.newBuilder(uri).
    timeout(Duration.ofSeconds(5)).
    build();
    
    HttpClient handler =
    HttpClient.newBuilder().
    version(HttpClient.Version.HTTP_2).
    build();
    
    HttpResponse.PushPromiseHandler<String> pp =
    (HttpRequest initiatingRequest, 
     HttpRequest pushPromiseRequest, 
     Function<HttpResponse.BodyHandler<String>,
     CompletableFuture<HttpResponse<String>>> acceptor)
     -> {
      acceptor.apply(HttpResponse.BodyHandlers.ofString()).
      thenAccept(respo -> {
        System.out.println("Pushed Response: " + respo.uri());
        System.out.println("Headers: " + respo.headers());
        System.out.println();
      });
      
      System.out.println
      ("Promise Request: " + pushPromiseRequest.uri());
      System.out.println
      ("Request Headers: " + pushPromiseRequest.headers());
    };
    
    CompletableFuture<HttpResponse<String>> response = 
    handler.sendAsync(pageRequest,
    HttpResponse.BodyHandlers.ofString(),pp);
    
    System.out.println
    ("Response Status Code: " + response.get().statusCode());
    System.out.println("Requested Page");
    System.out.println(response.get().body());
    
  }
}
HttpResponse.PushPromiseHandler has two methods. applyPushPromise method is an abstract method where push promise resources is being received. This is the method that is defined in pp variable.

of method is a static method that returns a push promise handler that accumulates push promises, and their responses, into the given map.

Tuesday, February 8, 2022

Java Tutorial: HttpURLConnection and HttpsURLConnection

Chapters

HttpURLConnection

HttpURLConnection is a URLConnection with support for HTTP-specific features. See the spec for details. This class is a subclass of URLConnection. You should be knowledgeable about URLConnection first before reading this tutorial. I already discussed URLConnection in this article.

This example demonstrates HttpURLConnection.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
                        
    URL url = new URL("https://www.google.com");
    HttpURLConnection huc = 
    (HttpURLConnection)url.openConnection();
    
    System.out.println
    ("Request Method: " + huc.getRequestMethod());
    System.out.println
    ("Response Code: " + huc.getResponseCode());
    System.out.println
    ("Response Message: " + huc.getResponseMessage());
    System.out.println
    ("Using Proxy? " + huc.usingProxy());
    
  }
}

Result
Request Method: GET
Response Code: 200
Response Message: OK
Using Proxy? False
The methods that I used above are pretty straightforward to understand. getRequestMethod() returns the request method of the domain like POST, GET, PUT, etc. You can find more about HTTP request methods in this article.

getResponseCode() returns the HTTP response code of the domain like 404, 302, 200, etc. getResponseMessage() returns the message associated with response code. You can find more information about response codes and messages in this article. usingProxy() returns a boolean value that denotes if the domain uses proxy or not.

Handling Redirects

We can use HttpURLConnection to handle redirects. Typically, redirects return response codes of Found(302), Moved Permanently(301) and See Other(303). More information about redirect response codes can be found in this article.

This example demonstrates on how to handle redirects manually.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                      throws Exception{
    URL url = 
    new URL("http://www.twitter.com");
    HttpURLConnection huc = 
    (HttpURLConnection)url.openConnection();
    
    HttpURLConnection.
    setFollowRedirects(false);
    huc.setInstanceFollowRedirects(true);
    System.out.println
    ("Original URL: " +huc.getURL());
    
    int responseCode = huc.getResponseCode();
    while(
    responseCode == HttpURLConnection.HTTP_MOVED_PERM ||
    responseCode == HttpURLConnection.HTTP_MOVED_TEMP ||
    responseCode == HttpURLConnection.HTTP_SEE_OTHER){
      
      URL redirectURL = 
      new URL(huc.getHeaderField("Location"));
      
      huc = (HttpURLConnection)redirectURL.openConnection();
      
      System.out.println("Redirect URL: " + huc.getURL());
      
      responseCode = huc.getResponseCode();
    }
  }
}

Result
Original URL: http://www.twitter.com
Redirect URL: https://www.twitter.com
Redirect URL: https://twitter.com
In the example above, we see that http://www.twitter.com has two redirects. getHeaderField("Location") returns the "Location" http header field value which is the redirect URL. setFollowRedirects(boolean set) is a static method that enables/disables automatic redirect follow of every HttpURLConnection in our program.

In the example above, I set it to false because I want to manually handle redirects. Manually handling redirects is good when dealing with a redirect from http to https and vice versa. setInstanceFollowRedirects(boolean followRedirects) determines if the instance that calls the method follows redirects. I set huc follow redirects flag to true because I wanna this instance to follow redirects.

Sometimes, a URLConnection will follow a redirect even we set setFollowRedirects() to false. Take a look at this example.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                      throws Exception{
    URL url = new URL("https://dropapk.to");
    HttpURLConnection huc = 
    (HttpURLConnection)url.openConnection();
    
    HttpURLConnection.
    setFollowRedirects(false);
    huc.setInstanceFollowRedirects(true);
    
    System.out.println
    ("Original URL: " +huc.getURL());
    System.out.println
    ("Response Code: " + huc.getResponseCode());
    System.out.println
    ("Redirect URL: " +huc.getURL());
    
  }
}

Result
Original URL: https://dropapk.to
Response Code: 200
Redirect URL: https://drop.download
To prevent automatic redirect in this case, we need to set setInstanceFollowRedirects() method to false. Take a look at this example.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                      throws Exception{
    URL url = new URL("https://dropapk.to");
    HttpURLConnection huc = 
    (HttpURLConnection)url.openConnection();
    huc.setInstanceFollowRedirects(false);
    
    System.out.println
    ("Original URL: " +huc.getURL());
    System.out.println
    ("Response Code: " + huc.getResponseCode());
    System.out.println
    ("Redirect URL: " +huc.getURL());
    
  }
}

Result
Original URL: https://dropapk.to
Response Code: 301
Redirect URL: https://dropapk.to
Even we set setFollowRedirects() to true, huc won't automatically follow redirects. Moreover, we can still get redirects manually since the response code is 301(Permanent Redirect).

Note that not every redirect has redirection response codes. For example, some fle hosting site returns 404 not found response code if the requested file is not found and redirects clients to another URL.


Reading From and Writing to a URL

Before testing the example below, assuming you're using apache server, create a directory in your document root directory and name it test. Then, create a php file and name it url_con_test.php. Then, put this in your php file.
<?php
echo str_shuffle($_GET['text']);
?>
This example demonstrates reading from and writing to a URL.
import java.net.*;
import java.io.*;

public class SampleClass{

  public static void main(String[] args)
                throws IOException,
                       MalformedURLException{
    URL url = new URL(
    "http://localhost/test/url_con_test.php?text=String");
    
    HttpURLConnection uc = 
    (HttpURLConnection)url.openConnection();
    
    uc.setRequestMethod("GET");
    uc.setDoOutput(true);
    uc.setConnectTimeout(5000);
    uc.setReadTimeout(3000);
    
    uc.addRequestProperty
    ("Accept-Language","en-US, en-BR; q=0.8");
    
    String stringToShuffle = 
    new String("Brainy Ghosts".getBytes(), "UTF-8");
    
    OutputStreamWriter out =
    new OutputStreamWriter(uc.getOutputStream());
    out.write("text="+stringToShuffle);
    out.close();
    
    BufferedReader in = 
    new BufferedReader(
      new InputStreamReader(
        uc.getInputStream()));
    
    String result = null;
    while ((result = in.readLine()) != null)
      System.out.println
      (new String(result.getBytes(), "UTF-8"));
    in.close();
  }
}

Result(may vary)
trngSi
setRequestMethod method sets the method for the URL request, one of: GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE are legal, subject to protocol restrictions. The default method is GET. The example above is similar to the example in this article that I've written. You can see the full explanation of the example there.

HttpsURLConnection

HttpsURLConnection extends HttpURLConnection with support for https-specific features. This class is recommended to be used when opening a connection with a URL that has HTTPS protocol. See this article and RFC 2818 for more details on the https specification.

This example demonstrates HttpsURLConnection.
import java.io.IOException;
import java.net.*;
import java.security.cert.Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException,
                       SSLPeerUnverifiedException,
                       IOException{
  
    URL url = new URL("https://www.google.com");
    HttpsURLConnection https = 
    (HttpsURLConnection)url.openConnection();
    
    System.out.println
    ("URL: " +https.getURL());
    System.out.println
    ("Response Code: " + https.getResponseCode());
    System.out.println
    ("Cipher Suite: " + https.getCipherSuite());
    
    System.out.println();
    //dump domain certificates
    Certificate[] certs = https.getServerCertificates();
	for(Certificate cert : certs){
	   System.out.println
       ("Cert Type: " + cert.getType());
	   System.out.println
       ("Cert Hash Code: " + cert.hashCode());
	   System.out.println
       ("Cert Public Key Algorithm: " 
       + cert.getPublicKey().getAlgorithm());
	   System.out.println
       ("Cert Public Key Format: " 
       + cert.getPublicKey().getFormat());
	   System.out.println();
	}
  }
}

Result
URL: https://www.google.com
Response Code: 200
Cipher Suite: TLS_AES_256_GCM_SHA384

Cert Type: X.509
Cert Hash Code: 1129315267
Cert Public Key Algorithm: EC
Cert Public Key Format: X.509

Cert Type: X.509
Cert Hash Code: 266225904
Cert Public Key Algorithm: RSA
Cert Public Key Format: X.509

Cert Type: X.509
Cert Hash Code: 1081005260
Cert Public Key Algorithm: RSA
Cert Public Key Format: X.509

Monday, February 7, 2022

Java Tutorial: Networking

Chapters

Overview

In this tutorial, we're going to discuss networking basics in java. java.net package provides the classes for implementing networking applications. We're going to discuss some classes in that package. In this overview, I gonna give short definition about components that are important in creating a program that requires networking.

There are two types of transport protocols that we need to learn: TCP(Transmission Control Protocol) and UDP(User Datagram Protocol). These two protocols and other networking related components are explained in this article. Protocol is simply a set of rules on how two or more entities are communicating to one another. Examples of protocols: TCP, UDP, FTP, etc.

URL or Uniform Resource Locator, colloquially termed a web address, is a reference to a web resource that specifies its location on a computer network and a mechanism for retrieving it. A URL is a specific type of Uniform Resource Identifier (URI). Example of URL: https://www.google.com

URI or Uniform Resource Identifier is a unique sequence of characters that identifies a logical or physical resource used by web technologies. URIs may be used to identify anything, including real-world objects, such as people and places, concepts, or information resources such as web pages and books.

Port Number is a 16-bit unsigned integer, thus ranging from 0 to 65535. For TCP, port number 0 is reserved and cannot be used, while for UDP, the source port is optional and a value of zero means no port. Port numbers are like identifiers for port in computer networking. Ports are divided into three sections:
Port numbers from 0 to 1023 are system or well-known ports.
Port numbers from 1024 to 49151 are user or registered ports.
Port numbers from 49152 to 65535 are dynamic / private / ephemeral ports.
For beginners, registered ports are preferrable to be used.

Internet Protocol address(IP address) is a numerical label such as 192.0.2.1 that is connected to a computer network that uses the Internet Protocol for communication. An IP address serves two main functions: network interface identification and location addressing. There are two types of IP address: IPv4 and IPv6

media access control address(MAC address) is a unique identifier assigned to a network interface controller (NIC) for use as a network address in communications within a network segment. MAC addresses are recognizable as six groups of two hexadecimal digits, separated by hyphens, colons, or without a separator.

Network Socket is a software structure within a network node of a computer network that serves as an endpoint for sending and receiving data across the network. The structure and properties of a socket are defined by an application programming interface (API) for the networking architecture.

Working With URLs

URL is an acronym for Uniform Resource Locator and is a reference (an address) to a resource on the Internet. URL has two main components: Protocol Identifier and Resource Name. For example, this URL: http://www.example.com, has two main components: http or Hypertext Transfer Protocol is a protocol identifier. :// is a separator and www.example.com is a resource name.

Resource name can have these components depend entirely on the protocol used: Host Name, Filename, Port Number, Reference.
Take a look at this URL:
http://www.example.com:4444/showstats.html#delivery

www.example.com is a host name.
4444 is a port number.
showstats.html is a filename.
#delivery is a reference.
Most websites don't need port numbers in their URLs. In this topic about URLs, we're going to use the URL class.
Creating URL

The easiest way to create a URL object is from a String that represents the human-readable form of the URL address. We already know the components of URL, now let's discuss the type types of URL: absolute and relative URLs. Absolute URL contains complete information about URL like protocol identifier, host name, etc.

Relative URL is like a shortcut. It omits the left portion of an absolute URL. For example, http://www.example.com/delivery/stats.html is an absolute URL. delivery/stats.html is a relative URL. Relative URL is mostly used within HTML files.

Now that we know absolute and relative URLs, we can now create a URL. URL class provides constructors for creating URLs.

This example demonstrates creating URLs using URL class.
import java.net.URL;
import java.net.MalformedURLException;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException{
    
    URL url1 = new URL("http://www.example.com");
    URL url2 = new URL(
    "https", "www.example.com", 
    "/delivery/stats.html");
    URL url3 = new URL(url1, "register.html");
    
    System.out.println("url1: " + url1);
    System.out.println("url2: " + url2);
    System.out.println("url3: " + url3);
  }
}

Result:
url1: http://www.example.com
url2: http://www.example.com/delivery/stats.html
url3: http://www.example.com/register.html
This constructor URL(String spec) creates a URL using spec String. url1 uses this constructor.

This constructor URL(String protocol, String host, String file) create a URL by combining multiple Strings that respectively denotes protocol identifier, host name and filename. We can use this constructor to make a relative URL become absolute URL. url2 uses this constructor.

This constructor URL(URL context, String spec) create a URL by getting another URL and then combine it with another String. This is another constructor used for making a relative URL become absolute. url3 uses this constructor.

URL class throws MalformedURLException. Make sure to explicitly handle it. Also remember that URL objects are immutable.

Parsing URL

URL class has accessor methods that parse URL. This example demonstrates some accessor methods that I'm talking about.
import java.net.URL;
import java.net.MalformedURLException;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException{
    
    URL url = new URL("https://www.example.com:4444");
    
    System.out.println("Port: " + url.getPort());
    System.out.println("Authority: " + url.getAuthority());
    System.out.println("Protocol: " + url.getProtocol());
    System.out.println("Host: " + url.getHost());
    
  }
}

Result
Port: 4444
Authority: www.example.com:4444
Protocol: https
Host: www.example.com
More methods can be seen in the documentation.

Reading from a URL Object

We can read the content of a an HTML file represented by a URL. We can read HTML commands and textual content from the HTML file. To do this, we're going to use openStream() method provided by URL class.

This example demonstrates openStream() method.
import java.net.URL;
import java.net.MalformedURLException;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class SampleClass{

  public static void main(String[] args)
                throws IOException, 
                MalformedURLException{
  
    URL url = new URL("https://www.google.com");
    
    try(BufferedReader bf = 
        new BufferedReader(
          new InputStreamReader(
            url.openStream()))){
        
        String content = null;
        while((content = bf.readLine()) != null)
          System.out.println(content);
    }
  }
}
Make sure you're connected to the internet and the website is online before executing the example above. Otherwise, an exception will be thrown. The example above might also hang or you might see an exception stack trace. If either of the latter two events occurs, you may have to set the proxy host.

If you want to try this example offline then you need to create a local server on your computer. You can use XAMPP to easily create a local server. Once you created a local server, run the server and change the URL in the example above to the URL of your local server. For example, if you're using XAMPP default settings, change the URL in the example above to this: http://localhost.

URLConnection Class

URLConnection is the superclass of all classes that represent a communications link between the application and a URL. This class is an abstract class so we can't instantiate it. However, we can use openConnection() method from URL class to instantiate an object related to this abstract class.

In URL class, we can parse the URL itself. In URLConnection we can parse headers of a document linked to a URL. We can also use URLConnection to read from and write to a server. More information on the request properties and header fields of an http connection can be found in this article.

This example demonstrates URLConnection accessor methods.
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                throws IOException,
                       MalformedURLException{
    URL url = new URL("https://www.google.com");
    URLConnection uc = url.openConnection();
    
    System.out.println("Encoding: " +
    uc.getContentEncoding());
    System.out.println("content-type: " +
    uc.getContentType());
    System.out.println("cache-control: " +
    uc.getHeaderField("Cache-Control"));
    
  }
}

Result
Encoding: null
content-type: text/html; charset=ISO-8859-1
cache-control: private, max-age=0
In the result above, getContentEncoding() because content-encoding of the URL is not known.

URLConnection has subclasses: HttpURLConnection and JarURLConnection. HttpURLConnection is a URLConnection that specialized in HTTP documents. This class supports HTTP-specific features which are discussed in this article.

JarURLConnection that works on Jar URLs. HttpsURLConnection is a subclass of HttpURLConnection. HttpsURLConnection supports HTTPS-specific features.

Using URLConnection to Read from and Write to a URL

To read from and write to a URL using URLConnection, we need to use getOutputStream() to write data and getInputStream() to read data. We can also check if a URLConnection is set to do input and output by using getDoInput() and getDoOutput() methods.

Before testing the example below, assuming you're using apache server, create a directory in your document root directory and name it test. Then, create a php file and name it url_con_test.php. Then, put this in your php file.
<?php
echo str_shuffle($_POST['text']);
?>
This example demonstrates reading from and writing to a URL using URLConnection.
import java.net.*;
import java.io.*;

public class SampleClass{

  public static void main(String[] args)
                throws IOException,
                       MalformedURLException{
    URL url = new URL("http://localhost/test/url_con_test.php");
    
    URLConnection uc = url.openConnection();
    uc.setDoOutput(true);
    uc.setConnectTimeout(5000);
    uc.setReadTimeout(3000);
    
    uc.addRequestProperty
    ("Accept-Language","en-US, en-BR; q=0.8");
    
    String stringToShuffle = 
    new String("Brainy Ghosts".getBytes(), "UTF-8");
    
    OutputStreamWriter out =
    new OutputStreamWriter(uc.getOutputStream());
    out.write("text="+stringToShuffle);
    out.close();
    
    BufferedReader in = 
    new BufferedReader(
      new InputStreamReader(
        uc.getInputStream()));
    
    String result = null;
    while ((result = in.readLine()) != null)
      System.out.println
      (new String(result.getBytes(), "UTF-8"));
    in.close();
  }
}

Result(may vary)
Gnhsosy iatrB
By default, getDoOuput() of URLConnection returns false. If we want a URLConnection to write to a URL, we need to invoke setDoOuput() and set it to true. First off, we need to write to a URL. That's why we need to get the underlying output stream of a URLConnection first and then write a String.

setConnectTimeout method sets the connection timeout between client and server. Connection timeout happens when a server takes time to respond to client's request. In the example above, I set the maximum waiting time to 5000 milliseconds of 5 seconds.

If the server is not responding to client's request for more than 5 seconds, the connection will time out or in other words, client can't connect to server. By default, java sets connection timeout to 0. It means that a client will wait for the server indefinitely.

setReadTimeout method sets the read timeout between client and server. Read timeout happens when a client is blocked for a long time while reading to a server. In the example above, I set the maximum waiting time to 3000 milliseconds of 3 seconds.

If the client is blocked while reading for more than 3 seconds, client will stop reading to the server. By default, java sets read timeout to 0. It means that a client will indefinitely wait for the input stream to be unblocked.

addRequestProperty method adds a general request property specified by a key-value pair. This method will not overwrite existing values associated with the same key. If you want to overwrite existing values of a request property, use setRequestProperty method. You can find more HTTP headers in this article.

The written String will be shuffled in the server and returns the shuffled String. To get the shuffled String, we need to get the underlying input stream of URLConnection. Take note that some URLs may not support input/output. If the URL does not support output, getOutputStream() method throws an UnknownServiceException.

URL Encoding

URLs can only be sent over the Internet using the ASCII character-set. Characters outside of this character set are encoded by replacing character with a "%" followed by two hexadecimal digits. This type of encoding is called Percent-encoding.

Some characters in the ASCII set are not allowed in some part of a URL. For example, "#" character has special meaning in URL. Using it as part of a filename will make a malfunctioned URL that can't locate a specified resource. For example: http://localhost/myspace#1.html.

These characters will be replaced with valid ASCII format.
For example, for space character, it will be replaced with "+" or "%20". More information about URL encoding can be found in this article.

To encode and decode URLs in java, we're going to use URLEncoder and URLDecoder. Encoded URL is often used in the submission of HTML form data in HTTP requests.

This example demonstrates URLEncoder and URLDecoder.
import java.net.*;
import java.io.UnsupportedEncodingException;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException,
                       UnsupportedEncodingException{
  
    URL url1 = new URL
    ("http://localhost/action.php?text="+
    URLEncoder.encode("my jelly#jam","UTF-8"));
    
    URL url2 = new URL("http://localhost/my%20space%231.html");
    
    System.out.println
    ("Encoded: " + url1);
    
    System.out.println
    ("Decoded: " + 
    URLDecoder.decode(url2.toString(),"UTF-8"));
  }
}

Result
Encoded: http://localhost/action.php?text=my+jelly%23jam
Decoded: http://localhost/my space#1.html

URI Class

A URI is a uniform resource identifier while a URL is a uniform resource locator. Hence every URL is a URI, abstractly speaking, but not every URI is a URL. This is because there is another subcategory of URIs, uniform resource names (URNs), which name resources but do not specify how to locate them. This is an example of URN: mailto:java-net@www.example.com

The conceptual distinction between URIs and URLs is reflected in the differences between this class and the URL class. An instance of this class represents a URI reference in the syntactic sense defined by RFC 2396. A URI may be either absolute or relative. A URI string is parsed according to the generic syntax without regard to the scheme, if any, that it specifies.

No lookup of the host, if any, is performed, and no scheme-dependent stream handler is constructed. Equality, hashing, and comparison are defined strictly in terms of the character content of the instance. In other words, a URI instance is little more than a structured string that supports the syntactic, scheme-independent operations of comparison, normalization, resolution, and relativization.

An instance of the URL class, by contrast, represents the syntactic components of a URL together with some of the information required to access the resource that it describes.

A URL must be absolute, that is, it must always specify a scheme. A URL string is parsed according to its scheme. A stream handler is always established for a URL, and in fact it is impossible to create a URL instance for a scheme for which no handler is available.

Equality and hashing depend upon both the scheme and the Internet address of the host, if any; comparison is not defined. In other words, a URL is a structured string that supports the syntactic operation of resolution as well as the network I/O operations of looking up the host and opening a connection to the specified resource. More information about this class can be found in the documentation.

This example demonstrates URI class.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException,
                       URISyntaxException{
    URL url = 
    new URL("http://localhost/my%20space/file%231.html");
    
    URI uri = url.toURI();
    
    System.out.println
    ("Is Absolute: " + uri.isAbsolute());
    
    System.out.println("Path: " + uri.getPath());
    
    System.out.println
    ("Raw Path: " + uri.getRawPath());
    
    URI uri2 = URI.create
    ("http://localhost/test1/test2/../testa/file.html");
    
    URL url2 = uri2.normalize().toURL();
    System.out.println("Normalized URL: " + url2);
    
    URI uri3 = URI.create
    ("http://localhost/test1");
    URI uri3a = 
    URI.create("http://localhost/test1/testb/fileb.html");
    System.out.println
    ("Relativize: "+uri3.relativize(uri3a));
    
    URI uri4 = URI.create
    ("http://localhost/");
    System.out.println
    ("Resolve: "
    +uri4.resolve(uri3.relativize(uri3a).toString()));
  }
}

Result
Is Absolute: true
Path: /myspace/file#1.html
Raw Path: /my%20space/file%231.html
Normalized URL: http://localhost/test1/testa/file.html
Relativize: testb/fileb.html
Resolve: http://localhost/testb/fileb.html

InetAddress Class

InetAddress class represents an Internet Protocol (IP) address. IP address has two versions: IPv4 and IPv6. InetAddress has Inet4Address and Inet6Address the represent IPv4 and IPv6 respectively.

This example demonstrates InetAddress, Inet4Address and Inet6Address.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                throws UnknownHostException{
    
    InetAddress inet = InetAddress.getByName("www.google.com");
    byte[] raw_ip = inet.getAddress();
    
    System.out.print("IP address(raw): ");
    
    for(int i = 0; i < raw_ip.length;i++)
      if(i < raw_ip.length-1)
        System.out.print(raw_ip[i]+".");
      else
        System.out.print(raw_ip[i]);
    System.out.println();
    
    Inet4Address inet4 = null;
    if(inet instanceof Inet4Address)
      inet4 = (Inet4Address)inet;
    else
      throw new NullPointerException("inet4 is null!");
      
    System.out.println
    ("IP Address(IPv4): " + inet4.getHostAddress());
    
    byte sixteen_octets[] =
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
    Inet6Address inet6 = 
    Inet6Address.getByAddress("local",sixteen_octets,0);
      
    System.out.println
    ("IP Address(IPv6): " + inet6.getHostAddress());
    
    System.out.println("Is inet4 a loopback address? "
    + inet4.isLoopbackAddress());
    System.out.println("Is inet4 a multicast address? "
    + inet4.isMulticastAddress());
    
    System.out.println("Is inet6 a loopback address? "
    + inet6.isLoopbackAddress());
    System.out.println("Is inet6 a multicast address? "
    + inet6.isMulticastAddress());
    
  }
}

Result(may vary)
IP address(raw): -84.-39.25.4
IP address(IPv4): 172.217.25.4
IP address(IPv6): 0:0:0:0:0:0:0:1%0
Is inet4 a loopback address? false
Is inet4 a multicast address? false
Is inet6 a loopback address? true
Is inet6 a multicast address? false
In the result above, the raw IP address uses signed bytes. the IPv4 IP address uses unsigned bytes. IPv4 octets are separated by dot(.). IPv6 consists of eight(16-bits hexadecimal) pairs. Each pair is separated by colon(:). %0 is the scope_id which is explained in the documentation.

This IPv6 address in the example above is a localhost address that is also called as loopback address. IPv6 address that is not localhost may look like this: 1080:0:0:0:8:800:200C:417A

Operational characteristics of an IP address like unicast, multicast, anycast and others determine how an IP address transmits data between entities. Multicast address has scopes like Link-local, Global scope, etc. You can read some information about those scopes in this article.

NetworkInterface

NetworkInterface represents a Network Interface made up of a name, and a list of IP addresses assigned to this interface. A network interface is the point of interconnection between a computer and a private or public network. A network interface is generally a network interface card (NIC), but does not have to have a physical form.

Instead, the network interface can be implemented in software. For example, the loopback interface (127.0.0.1 for IPv4 and ::1 for IPv6) is not a physical device but a piece of software simulating a network interface. The loopback interface is commonly used in test environments.
Source: What Is a Network Interface?.

This example gets all available network interface in our machine and display their names.
import java.net.*;
import java.util.Enumeration;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    Enumeration<NetworkInterface> interfaces =
    NetworkInterface.getNetworkInterfaces();
    
    while(interfaces.hasMoreElements()){
      NetworkInterface netInterface =
      interfaces.nextElement();
      System.out.println(netInterface.getDisplayName()
      + " | " + netInterface.getName());
      
    }
  }
}
One of the interfaces in the example above is this: Software Loopback Interface 1 | lo
This network interface refers to localhost. localhost is not physical network interface. The interfaces in the example above are collections of physical and non-physical network interfaces.

getDisplayName() returns the display name of a network interface. The display name can be its name shown in our operating system. getName() returns the name of a network interface. The name is the "network terminology" name of the network interface.

We can use NetworkInterface to select particular network interface in our machine. Take a look at this example.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    NetworkInterface loopback = 
    NetworkInterface.getByName("lo");
    
    System.out.println
    ("Is lo loopback? " + loopback.isLoopback());
    
    System.out.println
    ("IP addresses of lo network interface");
    loopback.inetAddresses()
    .forEach((t) -> 
     System.out.println(t.getHostAddress()));
  }
}

Result
Is lo loopback? true
IP addresses of lo network interface
127.0.0.1
0:0:0:0:0:0:0:1
Assume we don't know the IP addresses of localhost. By using NetworkInterface, we can get the IPv4 and IPv6 IP addresses of localhost. More information about the methods we used in the example above and other information regarding NetworkInterface can be found in the documentation.

Programming TCP Sockets

URL and URLConnection provide a relatively high-level mechanism for accessing resources on the Internet. Sometimes your programs require lower-level network communication, for example, when you want to write a client-server application.

Socket and ServerSocket are TCP sockets that we're going to use in this topic. More information about java TCP Sockets can be found in this article.

Reading from and Writing to a Socket

To create a server-client app, we need two classes: Server and Client classes. Server class handles server codes and Client class handles client codes. In this topic, we're going use loopback address or the localhost in order to test this server-client app only using a single computer. Also, we're going to use port 4444 as our port. You can use different port if you want and make sure the port that you're using is not being used.

This example requires two JVMs. If you're on windows, open two cmds and run Server.java on one cmd and run Client.java on another. Explanation about this topic can be seen below the examples.

Codes for Server.java
import java.net.*;
import java.io.*;

public class Server{

  public static void main(String[] args)
                     throws IOException,
                            ClassNotFoundException{
                            
    System.out.println
    ("Connecting server to port 4444...");      
    
    ObjectInputStream reader = null;
    ObjectOutputStream writer = null;
    try(ServerSocket ss = new ServerSocket(4444)){
      System.out.println
      ("Server is connected to port 4444!");
    
      System.out.println("Waiting for a client...");
      Socket s = ss.accept();
      System.out.println("Client is connected!");
    
      System.out.println("Getting client output...");
      StringBuilder reversed = new StringBuilder();
      reader = new ObjectInputStream(s.getInputStream());
    
      String clientOutput = (String)reader.readObject();
      System.out.println("Client Output");
      System.out.println(clientOutput);
      System.out.println();
      
      System.out.println("Sending reversed text...");
      writer = new ObjectOutputStream(s.getOutputStream());
      writer.writeObject(
      new String(new StringBuilder(clientOutput)
      .reverse().toString().getBytes(),
      "UTF-8"));
      writer.flush();
    
      System.out.println("Requested output is sent!");
      System.out.println("Client feedback");
      System.out.println(reader.readObject());
    }
    finally{
      if(reader != null)
        reader.close();
      if(writer != null)
        writer.close();
    }
  }
}
Codes for Client.java
import java.net.*;
import java.io.*;

public class Client{

  public static void main(String[] args)
                     throws IOException,
                            ClassNotFoundException{
                            
    System.out.println
    ("Connecting client to localhost via"+
     " port 4444...");
    ObjectOutputStream writer = null;
    ObjectInputStream reader = null;
    try(Socket s = new Socket("localhost",4444)){
      System.out.println
      ("Client is connected to localhost!");
    
      System.out.println
      ("Writing text to client socket...");
      String content = "dog\nrat\nkit";
      
      writer = new ObjectOutputStream(s.getOutputStream());
      writer.writeObject
      (new String(content.getBytes(),"UTF-8"));
      writer.flush();
      
      System.out.println("Writing complete!");
    
      System.out.println
      ("Waiting for server response...");
      reader = new ObjectInputStream(s.getInputStream());
          
      String serverOutput = (String)reader.readObject();
    
      System.out.println("Reversed Text");
      System.out.println(serverOutput);
      
      writer.writeObject
      (new String("Great Service!".getBytes(),"UTF-8"));
      writer.flush();
    }
    finally{
      if(reader != null)
        reader.close();
      if(writer != null)
        writer.close();
    }
  }
}
Result for Server.java
Connecting server to port 4444...
Server is connected to port 4444!
Waiting for a client...
Client is connected!
Getting client output...
Client Output
dog
rat
kit

Sending reversed text...
Requested output is sent!
Client feedback
Great Service!
Result for Client.java
Connecting client to localhost via port 4444...
Client is connected to localhost!
Writing text to client socket...
Writing complete!
Waiting for server response...
Reversed Text
tik
tar
god
Execute Server.java first before Client.java to make this example work. In Server.java, I instantiated ServerSocket "ss". ServerSocket is just like server's ear, listening to any socket that will try to communicate with the server.

In Client.java, I instantiated Socket "s". To establish a connection between client and server, client needs to instantiate a Socket class with location(IP address) of the server and the port where the server is listening to incoming connection.

In the example above, I use the "localhost" hostname. localhost is a known hostname for a loopback address. Thus, java will have no problem translating that hostname to an IP address behind the scene. In IPv4 format, localhost IP address is 127.0.0.1. Once the socket is bound to the port where the server is listening, It will try to connect and identify itself to the server via ServerSocket. Once the server accepts the socket, a new socket will be created.

This new socket is bound to the same local port and also has its remote endpoint set to the address and port of the client. It needs a new socket so that it can continue to listen to the original socket for connection requests while tending to the needs of the connected client. accept() method returns the new socket.

Once the server has the socket that is linked to the client, we can now read from and write to their sockets. InputStream and OutputStream that we get from getInputStream() and getOutpuStream are continuous streams. It means that they will be active unless our program is terminated or we explicitly close the streams.

Continuous streams don't give EOL(End of Line) unless they're closed via close() method. Thus, methods that rely on EOL like readLine() method from BufferedReader won't return null even there are no more data in the input stream.

If the client or server writes to or read from its socket, it will be blocked and will wait for the other to read from or write to its socket. When the other one is reading from or writing to its socket, it will be also blocked. Once the data transmission is complete, the client and the server will be unblocked.

Remember that Socket and ServerSocket are TCP(Transmission Control Protocol) sockets. TCP provides reliable, ordered, and error-checked delivery of a stream of octets (bytes) between applications running on hosts communicating via an IP network.

If you just wanna transmit text(String), I think it's better to use DataInputStream and DataOutputStream and use readUTF() and writeUTF() methods. In this way, you will encounter less data compatibility issues than using ObjectOutputStream and ObjectInputStream

ServerSocket can accept multiple client requests. Although, these requests are processed in sequential manner. However, the server can process requests simultaneously through the use of threads—one thread for each client connection. The basic flow of logic in such a server is this:
while (true) {
    accept a connection;
    create a thread to deal with the client;
}
Programming UDP Sockets

The UDP protocol provides a mode of network communication whereby applications send packets of data, called datagrams, to one another. A datagram is an independent, self-contained message sent over the network whose arrival, arrival time, and content are not guaranteed. The DatagramPacket and DatagramSocket classes in the java.net package implement system-independent datagram communication using UDP.
Source: Lesson: All About Datagrams

In terms of performance, UDP is much faster than TCP because UDP doesn't need a dedicated connection between two entities. Thus, UDP is often called connection-less protocol. The downside of UDP is that it's unreliable. During transmission using UDP, Some data packets may be lost and data packets may not be ordered.

UDP is often useful for streaming like video and audio streaming. These services may tolerate the unreliability of UDP. For example, during video call with UDP, a few lost data packets are not very noticeable. If our video call app uses TCP, the video stream may hang because TCP needs to ensure that data packets are sent from one socket to another.

Reading from and Writing to a DatagramSocket

DatagramSocket represents a socket for sending and receiving datagram packets. DatagramPacket represents a datagram packet.

So, to create data transmission using UDP, we need DatagramSocket. This socket receives from and sends to another DatagramSocket. Then, DatagramPacket holds bytes of data that are going to be read from or write to DatagramSocket. Before executing the example below, make sure that the ports that you're using are not being used. In the example below, ports 5555 and 4444 are used. You can use different ports if you want.

Codes for Client.java
import java.net.*;

public class Client{

  public static void main(String[] args)
                        throws Exception{
    
    System.out.println("Creating Datagram Socket...");
    try(DatagramSocket ds = new DatagramSocket(4444)){
      System.out.println("Datagram Socket Created!");
      System.out.println();
      
      String clientMessage = 
      "Hello! Hope you're doing great!";
      
      System.out.println
      ("Creating Datagram Packet to be Sent...");
      DatagramPacket sender = 
      new DatagramPacket(
      clientMessage.getBytes(),
      clientMessage.length(),
      InetAddress.getByName("localhost"),
      5555);
      System.out.println("Datagram Packet Created!");
      System.out.println();
      
      System.out.println("Sending Datagram Packet...");
      ds.send(sender);
      System.out.println("Datagram Packet Sent!");
      System.out.println();
      
      System.out.println("Receive Server Feedback...");
      byte[] buffer = new byte[1024];
      DatagramPacket receiver =
      new DatagramPacket(buffer, 1024);
      ds.receive(receiver);
      String feedback = 
      new String(receiver.getData(),"UTF-8").trim();
      
      System.out.println("Server Feedback");
      System.out.println(feedback);
    }
    
  }
}
Codes for Server.java
import java.net.*;

public class Server{

  public static void main(String[] args)
                        throws Exception{
    System.out.println("Creating Datagram Socket...");
    try(DatagramSocket ds = new DatagramSocket(5555)){
      System.out.println("Datagram Socket Created!");
      System.out.println();
      
      System.out.println("Receive Client Message...");
      byte[] buffer = new byte[1024];
      DatagramPacket receiver =
      new DatagramPacket(buffer, 1024);
      ds.receive(receiver);
      String message = 
      new String(receiver.getData(),"UTF-8").trim();
      System.out.println("Client Message");
      System.out.println(message);
      System.out.println();
      
      String serverMessage = 
      "Hi! I'm doing great!";
      
      System.out.println
      ("Creating Datagram Packet to be Sent...");
      DatagramPacket sender = 
      new DatagramPacket(
      serverMessage.getBytes(),
      serverMessage.length(),
      InetAddress.getByName("localhost"),
      4444);
      System.out.println("Datagram Packet Created!");
      System.out.println();
      
      System.out.println("Sending Datagram Packet...");
      ds.send(sender);
      System.out.println("Datagram Packet Sent!");
      
      ds.close();
    }
  }
}
Result for Client.java
Creating Datagram Socket...
Datagram Socket Created!

Creating Datagram Packet to be Sent...
Datagram Packet Created!

Sending Datagram Packet...
Datagram Packet Sent!

Receive Server Feedback...
Server Feedback
Hi! I'm doing great!
Result for Server.java
Creating Datagram Socket...
Datagram Socket Created!

Receive Client Message...
Client Message
Hello! Hope you're doing great!

Creating Datagram Packet to be Sent...
Datagram Packet Created!

Sending Datagram Packet...
Datagram Packet Sent!
Execute Server.java first before Client.java to make this example work. First off, in Client.java, I created a DatagramSocket that is bound to port 4444. In Server.java, the DatagramSocket there is bound to port 5555. We don't need to bind a client program if it's only a sender. In the example above, the client program is both sender and receiver. If a DatagramSocket receives data, we need to bind it to a port.

DatagramPacket constructors determine if the DatagramPacket receives data from or sends data to DatagramSocket. For example, this constructor DatagramPacket(byte[] buf, int length) Constructs a DatagramPacket for receiving packets. buf is the byte container where received bytes are stored. length is the maximum number of packets to be sent to buf. length must be less than or equal to buf.length.

This constructor DatagramPacket(byte[] buf, int length, InetAddress address, int port) constructs a datagram packet for sending packets. buf is the byte container where bytes that are gonna be sent are stored. length is the maximum numbers of bytes to be sent. address is the location of the receiver. port is the port where the receiver is bound to.

More information can be found in the documentation. We use send(DatagramPacket packet) method of DatagramSocket to send datagram packet. We use receive(DatagramPacket packet) to receive the sent datagram packet. To retrieve the bytes of data in DatagramPacket, we use getData() method. Lastly, don't forget to close DatagramSocket objects once you're done using them.

For broadcasting server response to multiple clients. We can use MulticastSocket class. In this article, There's a guide on how to broadcast server response to multiple clients.

Handling Cookies

A CookieHandler object provides a callback mechanism to provide an HTTP state management policy implementation in the HTTP protocol handler.

That is, URLs that use HTTP as the protocol, new URL("http://example.com") for example, will use the HTTP protocol handler. This protocol handler calls back to the CookieHandler object, if set, to handle the state management.

Java Web Start and Java Plug-in have a default CookieHandler installed. However, if you are running a stand-alone application and want to enable HTTP state management, you must set a system-wide handler. We will use CookieManager to set a system-wide cookie handler.

CookieManager provides a concrete implementation of CookieHandler, which separates the storage of cookies from the policy surrounding accepting and rejecting cookies. A CookieManager is initialized with a CookieStore which manages storage, and a CookiePolicy object.

CookiePolicy implementations decide which cookies should be accepted and which should be rejected. Three pre-defined policy implementations are provided, namely ACCEPT_ALL, ACCEPT_NONE and ACCEPT_ORIGINAL_SERVER.

ACCEPT_ALL accepts all cookies.
ACCEPT_NONE accepts no cookies.
ACCEPT_ORIGINAL_SERVER only accepts cookies from the original server. This is the default cookie policy of a CookieManager instance.

This example demonstrates handling of cookies in java.
import java.net.*;
import java.util.List;

public class SampleClass{

  public static void main(String[] args)
                      throws Exception{
    CookieManager cm = new CookieManager();
    CookieHandler.setDefault(cm);
    
    URL url = new URL("https://www.google.com");
    
    URLConnection uc = url.openConnection();
    System.out.println("Domain content-type");
    System.out.println(uc.getContentType());
    System.out.println();
    
    CookieStore store = cm.getCookieStore();
    List<HttpCookie> cookieList =
    store.getCookies();
    
    for(HttpCookie cookie : cookieList){
      System.out.println
      ("Domain: " + cookie.getDomain());
      System.out.println
      ("Cookie Name: " + cookie.getName());
      System.out.println
      ("Max Age: " + cookie.getMaxAge());
      System.out.println();
    }
  }
}

Result(may vary)
Domain content-type
text/html; charset-ISO-8859-1

Domain: .google.com
Cookie Name: NID
Max Age: 15811199

Domain: .google.com
Cookie Name: 1P_JAR
Max Age: 2591999
First off, we instantiate a CookieManager instance. This constructor CookieManager() creates a CookieManager instance with CookieStore and CookiePolicy. Moreover, The default CookiePolicy of this new CookieManager instance is ACCEPT_ORIGINAL_SERVER, which only accepts cookies from the original server. So, the Set-Cookie response from the server must have a “domain” attribute set, and it must match the domain of the host in the URL. For more information, see HttpCookie.domainMatches().

Static method setDefault(CookieHandler cHandler) of CookieHander sets a system-wide cookie handler. "system-wide" means throughout our application. Now that we have a system-wide handler, the specified CookieManager will call CookieStore.add to save cookies for every incoming HTTP response, and call CookieStore.get to retrieve cookie for every outgoing HTTP request.

To get an HTTP response from the domain, we need to send a request to the domain. URLConnection methods like getContent, getContentType() and some methods produce a request that can be sent to a domain.

To get accepted cookies from CookieManager, we need to invoke getCookieStore method of CookieManager. This method returns the underlying CookieStore instance of CookieManager. Then, Once we have CookieStore instance, we invoke getCookies() method.

getCookies() method returns a list of HttpCookie. HttpCookie represents an HTTP cookie, which carries state information between server and user agent. In the example above, I just used getter methods like getDomain(), getName() and getMaxAge(). We can also modify values in a cookie by using some setter methods in the HttpCookie class.

In the example above, cookies are stored in memory. It means that those cookies will be gone once our application is terminated. If you want cookies in your application to be persistent, you need to write those cookies in a persistent storage like in a hard drive. This article gives some idea on how to store cookies in a persistent storage. It also discusses about custom CookiePolicy, which gives some idea on how to blacklist certain domains.