Skip to main content
Java

Java 18: Simple Web Server and UTF-8 by Default

Ravinder··5 min read
JavaWeb ServerUTF-8Java 18
Share:
Java 18: Simple Web Server and UTF-8 by Default

Java 18: Simplifying Web Development

Java 18 (March 2022) introduced a simple, lightweight HTTP server API in the standard library. While short-lived (non-LTS), it demonstrates Java's commitment to simplifying common development tasks.

1. Simple Web Server API

Create HTTP servers without external frameworks.

Before Java 18:

// Using external library (Spring Boot, etc.)
// pom.xml dependency required
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

After Java 18:

import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.net.InetSocketAddress;
 
public class SimpleWebServer {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        
        // Simple GET endpoint
        server.createContext("/", exchange -> {
            String response = "Hello from Java 18!";
            exchange.getResponseHeaders().set("Content-Type", "text/plain");
            exchange.sendResponseHeaders(200, response.getBytes().length);
            exchange.getResponseBody().write(response.getBytes());
            exchange.close();
        });
        
        server.start();
        System.out.println("Server running on http://localhost:8080");
    }
}

Advanced HTTP Handler:

// Routing requests
public class APIServer {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        
        // GET /users
        server.createContext("/users", exchange -> {
            if ("GET".equals(exchange.getRequestMethod())) {
                String response = "[{\"id\":1,\"name\":\"John\"}]";
                sendJsonResponse(exchange, response, 200);
            } else {
                sendJsonResponse(exchange, "{\"error\":\"Method not allowed\"}", 405);
            }
        });
        
        // POST /users
        server.createContext("/api/users", exchange -> {
            if ("POST".equals(exchange.getRequestMethod())) {
                String body = new String(exchange.getRequestBody().readAllBytes());
                // Process body
                String response = "{\"id\":2,\"status\":\"created\"}";
                sendJsonResponse(exchange, response, 201);
            }
        });
        
        server.setExecutor(Executors.newFixedThreadPool(10));
        server.start();
    }
    
    private static void sendJsonResponse(HttpExchange exchange, 
                                        String response, int code) throws IOException {
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(code, response.getBytes().length);
        exchange.getResponseBody().write(response.getBytes());
        exchange.close();
    }
}

2. UTF-8 by Default

UTF-8 is now the default charset everywhere in the platform.

// Before Java 18: Charset varied by platform
// Windows: Windows-1252 (Western European)
// Unix/Linux: UTF-8
// Result: Cross-platform issues
 
// Files.readString() - platform-dependent
String content = Files.readString(Path.of("file.txt"));
// Charset was: US-ASCII on old systems
 
// After Java 18: Explicit UTF-8
String content = Files.readString(Path.of("file.txt")); // Now UTF-8!
 
// String encoding/decoding - now consistent
String encoded = "Hello 世界 🌍".getBytes().toString(); // Consistent UTF-8
 
// File I/O with emoji and international characters
String multilingual = "English, 中文, العربية, 日本語, 한글";
Files.writeString(Path.of("multilingual.txt"), multilingual);
 
// All text is now properly UTF-8 encoded
// No more platform-specific charset issues!

Benefits:

// Before: Had to specify encoding
ObjectInputStream input = new ObjectInputStream(
    Files.newInputStream(Path.of("file.dat"))
);
 
// Now: UTF-8 by default for all text operations
PrintWriter writer = new PrintWriter(
    Files.newBufferedWriter(Path.of("output.txt"))
);
writer.println("Internationalization: " + "مرحبا, こんにちは, 你好");

3. Finalized Features

Several preview features became standard.

Pattern Matching for switch (Preview 4):

// Still evolving, but approaching finalization
public String describePerson(Object person) {
    return switch (person) {
        case String s -> "Name: " + s;
        case Integer age when age < 13 -> "Child";
        case Integer age when age < 20 -> "Teen";
        case Integer age -> "Adult";
        default -> "Unknown";
    };
}

4. Stream API Enhancements

New stream operations for processing.

// Stream.toList() - shorthand for collect(Collectors.toList())
List<Integer> numbers = Stream.of(1, 2, 3, 4, 5)
    .filter(n -> n > 2)
    .toList(); // Immutable list
 
// Simpler than: .collect(Collectors.toList());
 
// Practical example
List<User> activeUsers = userStream
    .filter(User::isActive)
    .toList(); // Much cleaner!

5. Foreign Function & Memory API (Preview 2)

Enhanced APIs for calling native code safely.

// Calling native strlen() function
import java.lang.foreign.*;
 
public class NativeInterop {
    public static void main(String[] args) throws Throwable {
        SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
        Linker linker = Linker.nativeLinker();
        
        // Find strlen function
        MemorySegment strlenAddr = stdlib.find("strlen").get();
        
        // Create function descriptor
        FunctionDescriptor strlen = FunctionDescriptor.of(
            ValueLayout.JAVA_LONG, 
            ValueLayout.ADDRESS
        );
        
        // Get downcall handle
        MethodHandle strlenHandle = linker.downcallHandle(
            strlenAddr,
            strlen
        );
        
        // Call native function
        Arena arena = Arena.ofAuto();
        MemorySegment cString = arena.allocateUtf8String("Hello");
        long length = (long) strlenHandle.invoke(cString);
        System.out.println("Length: " + length); // 5
    }
}

6. Deprecation Warnings

More APIs marked for future removal.

// Enhanced deprecation warnings
import java.lang.Deprecated;
 
@Deprecated(since = "17", 
           forRemoval = true,
           message = "Use newAPI() instead")
public void oldAPI() { }
 
// Compiler warnings:
// warning: [removal] oldAPI() in OldClass is deprecated 
// and marked for removal

Developer Impact

Positive:

  • Simplified HTTP: No framework needed for simple APIs
  • Internationalization: UTF-8 consistent across platforms
  • Cleaner Code: Stream.toList() shorthand
  • Cross-platform: No charset issues

Challenges:

  • Limited HTTP API: Not suitable for complex applications
  • Short Support: Non-LTS with 6-month lifecycle
  • Learning Curve: Foreign Function API still incubating

Pros and Cons

Pros ✅

  • Simple Web Server: Create HTTP servers without frameworks
  • UTF-8 Default: Eliminates cross-platform charset issues
  • Stream.toList(): Shorter, more readable code
  • Less Boilerplate: Common tasks simplified
  • Foreign Function API: Safer native code interop

Cons ❌

  • Limited HTTP Server: Not production-ready for complex apps
  • Short Support: Non-LTS release with 6-month window
  • Learning Curve: Foreign Function API complex
  • No Major Features: Incremental improvements only

Practical Example

// Quick REST API with Java 18
import com.sun.net.httpserver.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
 
public class TodoAPI {
    static class TodoService {
        List<String> todos = new CopyOnWriteArrayList<>();
        
        public void addTodo(String todo) { todos.add(todo); }
        public List<String> getTodos() { return new ArrayList<>(todos); }
    }
    
    public static void main(String[] args) throws IOException {
        TodoService service = new TodoService();
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        
        server.createContext("/todos", exchange -> {
            if ("GET".equals(exchange.getRequestMethod())) {
                String response = service.getTodos().toString();
                exchange.getResponseHeaders().set("Content-Type", "application/json");
                exchange.sendResponseHeaders(200, response.getBytes().length);
                exchange.getResponseBody().write(response.getBytes());
            }
            exchange.close();
        });
        
        server.setExecutor(Executors.newFixedThreadPool(10));
        server.start();
        System.out.println("TODO API running on http://localhost:8080/todos");
    }
}

Conclusion

Java 18 shows Java's focus on developer experience with simpler APIs and better defaults. The simple web server API removes the need for frameworks for basic use cases. UTF-8 by default eliminates a major source of cross-platform bugs.

Not recommended for new projects, but useful for understanding Java's evolution toward simplicity. Join January 2023 for Java 21 LTS.

Key Points:

  • Simple HTTP API is useful for prototypes and microservices
  • UTF-8 default ensures global text handling
  • Stream improvements make code more concise
  • Short support window suggests staying on LTS releases