Remote Debug a Pod’s Java Process

Simple steps for remote debugging a Java process running on a k8 pod:

  1. Edit deployment and add the following parameters to the Java start line: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005

    Also add the following port mapping at the section container → ports in the deployment:

    - containerPort: 5005
      protocol: TCP
  2. Safe, wait for the new pods and then add a port forward for port 5005 for this pod:

    kubectl port-forward podname 5005
  3. Now add a new run configuration in IntelliJ Edit Configurations → Add New Configuration → Remote JVM Debug

  4. Set breakpoints, happy debugging! :)

Simple One-Class HTTP Server for Testing Purpose

HttpServe.java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.time.ZonedDateTime;

public class HttpServe {

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(9000), 0);
        server.createContext("/", new CustomHandler());
        server.setExecutor(null);
        server.start();
    }

    static class CustomHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = String.format("Now it is %s", ZonedDateTime.now());
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

}

Copy & Paste on a remote system with one click:

cat << EOF > HttpServe.java && javac HttpServe.java && java HttpServe
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.time.ZonedDateTime;

public class HttpServe {

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(9000), 0);
        server.createContext("/", new CustomHandler());
        server.setExecutor(null);
        server.start();
    }

    static class CustomHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = String.format("Now it is %s", ZonedDateTime.now());
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

}
EOF

Calling the service:

$ curl -s localhost:9000
Now it is 2022-10-27T08:09:46.387727300+02:00[Europe/Berlin]
Since Java 18 there is a webserver included, jwebserver: more information

Print Diagnostic RAM, CPU Information

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

private static void logResourceUsage() {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

var cpuInfos = new StringBuilder();
for (Long threadID : threadMXBean.getAllThreadIds()) {
    ThreadInfo info = threadMXBean.getThreadInfo(threadID);
    var cpuInfo = String.format("""
		    ---
		    Thread name: %s
		    Thread State: %s
		    CPU time: %s ns
		    """,
	    info.getThreadName(),
	    info.getThreadState(),
	    threadMXBean.getThreadCpuTime(threadID)
    );
    cpuInfos.append(cpuInfo);
}

var info = String.format("""
		=================================================
		Initial memory: %.2f GB
		Used heap memory: %.2f GB
		Max heap memory: %.2f GB
		Committed memory: %.2f GB
		Thread-Infos:
		%s
		=================================================
			""",
	((double) memoryMXBean.getHeapMemoryUsage().getInit() / 1073741824),
	((double) memoryMXBean.getHeapMemoryUsage().getUsed() / 1073741824),
	((double) memoryMXBean.getHeapMemoryUsage().getMax() / 1073741824),
	((double) memoryMXBean.getHeapMemoryUsage().getCommitted() / 1073741824),
	cpuInfos
);

        System.out.println(info);
    }

Show processes

jps

Create heap dump from running process

jmap -dump:format=b,file=/tmp/heap.bin 2381

Create jhat webserver to show heap dump analysis

jhat -port 7401 -J-Xmx4G heap.bin

Caller / Calling class from Stacktrace

StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
String className = stackTrace[2].getClassName();

Print Thread Dump with Deadlock Analysis

jcmd PID Thread.print

Recompile Class and Set Method Return to true

  • Download Recaf from GitHub: https://github.com/Col-E/Recaf

  • Open Jar file in Recaf

  • Find the designated class and method

  • Right-click on the method and select Opcodes

  • Select the last line with opcode IRETURN

  • Select New Opcode before..

  • Select Insn in the first dropdown and ICONST_1 as opcode from the second dropdown and click on Add opcode

  • Save the modified Jar-file (File – Save) and enjoy

Set Timezone in Unit Test

@Before
public void setup(){
  TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}

Starting Java with Debugging enabled

java -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y -jar target/vertx-websocket-chat-1.0.0-fat.jar

Debugging with jdb

Connecting

jdb -attach localhost:8000 -sourcepath /project/hascode-sample/src/main/java

Set breakpoint

stop in com.hascode.sample.Example.doSth(java.lang.String, java.lang.String)

Breakpoint hit

Outputs the thread-name (e.g. “thread=http-nio-8080-exec-1″), class-name, method-name, line-number and bci-counz (byte-code-instructions)

Exploring code at breakpoint

http-nio-8080-exec-1[1] list

Exploring data at breakpoint

---
http-nio-8080-exec-1[1] locals

http-nio-8080-exec-10[1] print VARIABLE ---

Add JUnit5 dependencies

<dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <scope>test</scope>
    </dependency>
</dependencies>

JUnit BeforeAll without static

The test class must be configured not to use TestInstance.Lifecycle.PER_METHOD which is the default, but TestInstance.Lifecycle.PER_CLASS:

MySpecialTest.java
package com.hascode.tutorial;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.Test;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MySpecialTest {
  String authToken;

  @BeforeAll
  void setup(){
    authToken = fetchAuthtoken();
  }

  @Test
  void mustDoSomething(){}

}

JUnit Parameterized Test with CSV Source

SampleParameterizedTest.java
package com.hascode.tutorial;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;

public class SampleParameterizedTest {

    @ParameterizedTest(name = "[{index}] User with name <{0}> should be <{1}> years old")
    @CsvSource({"Tim,22","Sally,43","Tina,19","Fred,84"})
    @SuppressWarnings("unused")
    void userNameAndAgeMustMatch(String userName, int age) {
        var user = fetchUser(userName);
        assertThat(user.age()).isEqualTo(age);
	assertThat(user.name()).isEqualTo(name);
    }
}

The same example with a nice, more tabellaric view using pipes as delimiter string:

@ParameterizedTest(name = "[{index}] User with name <{0}> should be <{1}> years old")
@CsvSource(delimiterString = "|", textBlock = """
    Tim   | 22
    Sally | 43
    Tina  | 19
    Fred  | 84
""")
void userNameAndAgeMustMatch(String userName, int age) {
    var user = fetchUser(userName);
    assertThat(user.age()).isEqualTo(age);
    assertThat(user.name()).isEqualTo(userName);
}

Looks like this when ran in IntelliJ:

junit csvsource test intellij
Figure 1. CSV Source Test ran in IntelliJ IDEA

Add Failsafe Plugin

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
</plugin>

Use SLF4J and Logback with Maven

Maven dependencies:

<project>
    <properties>
      <logback.version>1.0.3</logback.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.6.6</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
    </dependencies>
</project>

Example logback.xml:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="debug">
      <appender-ref ref="STDOUT"/>
    </root>
</configuration>

Logstash Logback JSON Logging Configuration

If log messages of logged exceptions are swallowed, it might be because the stacktraces are too long, the following configuration is helping!
<?xml version="1.0"?>
<configuration>
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <fieldName>timestamp</fieldName>
                    <pattern>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</pattern>
                    <timeZone>UTC</timeZone>
                </timestamp>
                <logLevel/>
                <threadName>
                    <fieldName>thread</fieldName>
                </threadName>
                <loggerName>
                    <fieldName>logger</fieldName>
                </loggerName>
                <message/>
                <stackTrace>
                    <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
                        <maxLength>256</maxLength>
                        <shortenedClassNameLength>20</shortenedClassNameLength>
                        <exclude>^sun\.reflect\..*\.invoke</exclude>
                        <exclude>^net\.sf\.cglib\.proxy\.MethodProxy\.invoke</exclude>
                        <exclude>^org\.springframework\..*\.InvocableHandlerMethod</exclude>
                        <exclude>^org\.springframework\..*\.RequestMappingHandlerAdapter\.invokeHandlerMethod</exclude>
                        <exclude>^org\.springframework\..*\.AbstractHandlerMethodAdapter\.handle</exclude>
                        <exclude>^org\.springframework\..*\.DirectMethodHandleAccessor\.invoke</exclude>
                        <exclude>^org\.springframework\..*\.FrameworkServlet\.java</exclude>
                        <exclude>^org\.springframework\..*\.ApplicationFilterChain\.doFilter</exclude>
                        <exclude>^java\.lang\..*\.Method\.invoke</exclude>
                    </throwableConverter>
                </stackTrace>
                <contextName/>
                <logstashMarkers/>
                <arguments/>
            </providers>
        </encoder>
    </appender>
    <root>
        <appender-ref ref="stdout"/>
    </root>
</configuration>

Get Class Information from Generic Class without Constructor Parameter

Class<T> entityClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

Externalizable Example

package com.hascode.sample;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.time.ZonedDateTime;

public class User implements Externalizable {

  private String name;
  private ZonedDateTime birthday;

  public User(){}
  public User(String name, ZonedDateTime birthday) {
    this.name = name;
    this.birthday = birthday;
  }

  public String getName() {
    return name;
  }

  public ZonedDateTime getBirthday() {
    return birthday;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder("User{");
    sb.append("name='").append(name).append('\'');
    sb.append(", birthday=").append(birthday);
    sb.append('}');
    return sb.toString();
  }

  @Override
  public void writeExternal(ObjectOutput out) throws IOException {
    out.writeUTF(name);
    out.writeObject(birthday);
  }

  @Override
  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    this.name = in.readUTF();
    this.birthday = (ZonedDateTime) in.readObject();
  }
}
package com.hascode.sample;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.ZonedDateTime;

public class Main {

  public static void main(String[] args) throws Exception {
    User user = new User("Fred", ZonedDateTime.now());

    // serializing
    try (FileOutputStream fos = new FileOutputStream(
        "user.ser"); ObjectOutputStream outStream = new ObjectOutputStream(fos)) {
      System.out.printf("serializing user: %s\n", user);
      outStream.writeObject(user);
    }

    // deserializing
    try (FileInputStream fis = new FileInputStream(
        "user.ser"); ObjectInputStream in = new ObjectInputStream(fis)) {
      User user1 = (User) in.readObject();
      System.out.printf("deserialized user: %s\n", user1);
    }
  }
}

Writing a custom Classloader

package io.hascode.tutorial;

public class ClassLoadSample {
    public static class MyCustomClassLoader extends ClassLoader{
        protected MyCustomClassLoader() {
            super(MyCustomClassLoader.class.getClassLoader());
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            System.out.println("searching for class "+name);
            return super.findClass(name);
        }
    }

    static class SampleClass {
    }



    public static void main(String[] args) {
        var myloader = new MyCustomClassLoader();
        try {
            var clazz = myloader.loadClass("io.hascode.tutorial.ClassLoadSample$SampleClass");
            System.out.println(clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

Lambda Expression implementing multiple Interfaces

Object r = (Runnable & Serializable) () -> {};

CDI / Weld Logging for GlassFish

add to domaindir/config/logging.properties

org.jboss.weld.level=FINE

Create full self-contained application image with jpackage

Creating an application image for a modular application
jpackage --type app-image -n name -p modulePath -m moduleName/className
Creating an application image for a nonmodular application
jpackage --type app-image -i inputDir -n name --main-class className --main-jar out.jar

Service Discovery using ServiceLoader

package com.hascode.api;
interface MyService {}
package com.hascode.impl1;
class MyServiceImpl implements MyService {}
package com.hascode.impl2;
class AnotherMyServiceImpl implements MyService {}
ServiceLoader serviceLoader = ServiceLoader.load(MyService.class);
for (MyService s : serviceLoader) {
System.out.println(s);
}
// SPI Mechanism to load from external jar-file
package com.someotherprovider;
class CustomMyServiceImpl implements MyService {}
// create a file named com.hascode.api.MyService in META-INF/services with following content:

com.someotherprovider.CustomMyServiceImpl

Disassemble Java Class

javap -c Sample.class

Debugging SSL/TLS Connection Problems

Add the following JVM start parameter

java -Djavax.net.debug=ssl:handshake:verbose

or for more logging:

java -Djavax.net.debug=ssl:handshake:verbose:keymanager:trustmanager -Djava.security.debug=access:stack

Resources:

View cert > Details > Save to file (format: Base 64 X.509 / .CER)

keytool -import -file /tmp/downloadedCert.cer -keystore /tmp/myKeystore

Default password here is: changeit

Keystore passed as parameter to Maven:

mvn -Djavax.net.ssl.trustStore=/tmp/myKeystore

List Certificates in Keystore

keytool -list -keystore /etc/ssl/certs/java/cacerts

Read SSL Certificate Information from pem file

keytool -printcert -file certificate.pem

Download SSL Certificate from Host/Post

Supported since Java 9 ..

keytool -printcert -rfc -sslserver HOST:PORT

Log4j 1.x log by package to different log files

Main class calling two classes in separate packages

package com.hascode.tutorial;

import com.hascode.tutorial.alpha.Foo;
import com.hascode.tutorial.beta.Bar;

public class Main {
public static void main(String[] args) {
final String input = "hello, logger";
new Foo().doSth(input);
new Bar().doSth(input);

	}
}

Class in package com.hascode.tutorial.alpha

package com.hascode.tutorial.alpha;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Foo {
private final Logger log = LoggerFactory.getLogger(getClass());

	public void doSth(String input) {
		log.info("doSth called with parameter {}", input);
		System.out.println("foo " + input);
	}
}

Class in package com.hascode.tutorial.beta

package com.hascode.tutorial.beta;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Bar {
private final Logger log = LoggerFactory.getLogger(getClass());

	public void doSth(String input) {
		log.info("doSth called with parameter {}", input);
		System.out.println("bar " + input);
	}
}

Configuration file log4j.properties

log4j.rootLogger=DEBUG, CONSOLE

log4j.logger.com.hascode.tutorial.alpha=DEBUG, ALPHA
log4j.logger.com.hascode.tutorial.beta=DEBUG, BETA

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n

log4j.appender.ALPHA=org.apache.log4j.RollingFileAppender
log4j.appender.ALPHA.File=./alpha.log
log4j.appender.ALPHA.layout=org.apache.log4j.PatternLayout
log4j.appender.ALPHA.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n

log4j.appender.BETA=org.apache.log4j.RollingFileAppender
log4j.appender.BETA.File=./beta.log
log4j.appender.BETA.layout=org.apache.log4j.PatternLayout
log4j.appender.BETA.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n

Running the program writes to console and following log files:

alpha.log

2016-05-19 15:22:30 INFO  com.hascode.tutorial.alpha.Foo:10 - doSth called with parameter hello, logger

beta.log

2016-05-19 15:22:30 INFO  com.hascode.tutorial.beta.Bar:10 - doSth called with parameter hello, logger

Full sources available here.

Java FX 2 Maven Dependency

<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>javafx</artifactId>
    <version>2.2</version>
    <systemPath>${java.home}/lib/jfxrt.jar</systemPath>
    <scope>system</scope>
</dependency>

Java FX 2 Fullscreen Stage

@Override
public void start(final Stage stage) throws Exception {
    Screen screen = Screen.getPrimary();
    Rectangle2D bounds = screen.getVisualBounds();
    stage.setX(bounds.getMinX());
    stage.setY(bounds.getMinY());
    stage.setWidth(bounds.getWidth());
    stage.setHeight(bounds.getHeight());
    [...]
}

WildFly Deploy via Console

Deploy

jboss-cli.sh --connect --command="deploy target/myapp-1.0.0.war"

Deployment Status

jboss-cli.sh --connect --command=deployment-info

Undeploy

jboss-cli.sh --connect --command="undeploy myapp-1.0.0.war"

Patching WildFly

$ sh jboss-cli.sh
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
[standalone@localhost:9990 /] patch apply ~/Downloads/wildfly-8.1.0.Final
wildfly-8.1.0.Final-weld-2.2.2.Final-patch.zip  wildfly-8.1.0.Final.tar.gz
[standalone@localhost:9990 /] patch apply ~/Downloads/wildfly-8.1.0.Final-weld-2.2.2.Final-patch.zip
{
"outcome" : "success",
"response-headers" : {
"operation-requires-restart" : true,
"process-state" : "restart-required"
}
}

Dynamic Proxy

package com.hascode.sample;

public interface SampleService {
String printInformation();
}
package com.hascode.sample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class SampleServiceInvocationHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
  return "test";
}
}
package com.hascode.sample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
    InvocationHandler handler = new SampleServiceInvocationHandler();
    SampleService service = (SampleService) Proxy.newProxyInstance(SampleService.class.getClassLoader(), new Class[]{SampleService.class}, handler);
    System.out.println(service.printInformation());
}
}

Java Receiver Parameters

Available since Java 8 (link)

package com.hascode.tutorial;

public class Foo {
void foo(@SampleAnnotation1 Foo this) {
}

	class Bar {
		Bar(@SampleAnnotation2 Foo Foo.this) {

		}
	}
}

Add Class Diagrams to the JavaDocs using Maven and GraphViz

Step 1: Install graphviz:

sudo apt-get install graphviz

Step 2: Modify maven reports

<reporting>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>2.10.3</version>
            <configuration>
                <doclet>org.umlgraph.doclet.UmlGraphDoc</doclet>
                <docletArtifact>
                    <groupId>org.umlgraph</groupId>
                    <artifactId>doclet</artifactId>
                    <version>5.1</version>
                </docletArtifact>
                <show>private</show>
                <additionalparam>-all -constructors</additionalparam>
                <useStandardDocletOptions>false</useStandardDocletOptions>
            </configuration>
        </plugin>
    </plugins>
</reporting>

Step 3: Generate JavaDocs using Maven

mvn site

Separating Unit and Integration Tests with JUnit Categories

Create a marker interface for the JUnit category:

package com.hascode;

public interface IntegrationTest {}

Write an integration test and annotate it with @Category using the marker interface as value

import com.hascode.IntegrationTest;
import org.junit.experimental.categories.Category;

@Category(IntegrationTest.class)
public class MyIntegrationTest {
// ...
}

Now configuring the Maven Surefire Plugin (Unit-Tests) and the Failsafe Plugin (Integration-Tests)

<plugins>
    <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.19.1</version>
        <configuration>
          <excludedGroups>com.hascode.IntegrationTest</excludedGroups>
        </configuration>
    </plugin>
    <plugin>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.19.1</version>
        <configuration>
            <includes>
                <include>**/*.java</include>
            </includes>
            <groups>com.hascode.IntegrationTest</groups>
        </configuration>
        <executions>
            <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

Unit tests may be run now with mvn test, integration tests with mvn verify or mvn integration-test.

JAX-RS return a generic list in a Response

List<Book> list = new ArrayList<>();
GenericEntity<List<Book>> entity = new GenericEntity<List<Book>>(list) {};
Response response = Response.ok(entity).build();

JAX-RS / Jersey – Return a JSON Array for a Collection with a single element

Lets say you’re mapping a collection of books e.g. List<Book> books using jaxb passed as a jaxrs Response .. you may encounter a situation where you receive the following JSON output if there are many books in the collection:

{
    books:[
        {id:1, title:"foo"},
        {id:2, title:"bar"}
    ]
}

But if there is only one book in the collection your receive a single JSON object instead of an array .. e.g.:

{
    books:{
      id:1, title:"foo"
    }
}

The solution is to provide a custom JAXBContext ContextResolver and advise the context always to render arrays here:

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;

import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;

import com.hascode.entity.Book;

@Provider
public class BookContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Class[] types = { Book.class };

        public PersonContextResolver() throws Exception {
                this.context = new JSONJAXBContext(JSONConfiguration.mapped().arrays("books").build(), Book.class);
        }

        @Override
        public JAXBContext getContext(final Class<?> objectType) {
                for (Class type : types) {
                        if (type == objectType) {
                                return context;
                        }
                }
                return null;
        }
}

Increase Eclipse Memory Settings

In the eclipse/configuration directory edit the config.ini

--launcher.XXMaxPermSize
128M
-Xms1024m
-Xmx2048m
-XX:MaxPermSize=1048m

Different Logging Library Placeholders

SLF4J

log.info("{}, {}", "Hello", "World");

JULI Logger

log.log(Level.INFO, "{0}, {1}", new Object[]{"Hello", "World"});

Seam Logger

log.info("#0, #1", "Hello", "World");

Create heap dump on OutOfMemoryError

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/save/dumps

Java Bean Validation add custom Validation Cases and Messages

Use the javax.validation.ConstraintValidatorContext to override the default validation error message.

Given is a simple bean validator for NIO Path objects:

PathIsAccessible.java
package com.hascode.example;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = PathIsAccessibleValidator.class)
@Documented
public @interface PathIsAccessible {

  String message() default "PathIsAccessible"; (1)

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

}

And this the designated validator for our annotation marked fields:

PathIsAccessibleValidator.java
public class PathIsAccessibleValidator implements ConstraintValidator<PathIsAccessible, Path> {

  @Override
  public boolean isValid(Path path, ConstraintValidatorContext ctx) {
    ctx.disableDefaultConstraintViolation(); (2)
    if (path == null) {
      ctx.buildConstraintViolationWithTemplate("{PathIsAccessible.IsNull}") (3)
          .addConstraintViolation();
      return false;
    }

    if (!path.toFile().exists()) {
      ctx.buildConstraintViolationWithTemplate("{PathIsAccessible.NonExistent}") (4)
          .addConstraintViolation();
      return false;
    }

    return true;
  }
}
1 this is the default validation error message
2 we’re deactivating the default error message here
3 we’re setting a more specific error message
4 we’re setting another more specific error message

Initialize XMLGregorianCalendar by lexical string representation

e.g. when using ugly legacy SOAP APIs.

XMLGregorianCalendar calendar = DatatypeFactory.newInstance().newXMLGregorianCalendar("2022-12-24T13:56:00.000+01:00");

Java HTTP Proxy Configuration

Proxy configuration is done by setting the following JVM parameters:

http.proxyHost

Proxy server host used for HTTP

http.proxyPort

Proxy server port used for HTTP

https.proxyHost

Proxy server host used for HTTPS

https.proxyPort

Proxy server port used for HTTPS

httpNonProxyHosts

List of proxy excemptions, separated by a pipe '|'

http.proxyUser

User for proxy authentication

http.proxyPassword

Password for proxy authentication

So a sample Java invocation with proxy settings added, might look like this one:

java -Dhttp.proxyHost=myproxyhost.io -Dhttp.proxyPort=8080 -Dhttps.proxyHost=myproxyhost.io -Dhttps.proxyPort=8080 -DhttpNonProxyHosts=127.0.0.1|localhost|169.254.169.254|amazonaws.com

Create Docker Image for Java Web App

Dockerfile
FROM openjdk:17-jdk-alpine
RUN mkdir -p /usr/opt/my-app
COPY target/*.jar /usr/opt/my-app/my-app.jar
EXPOSE 8080
ENTRYPOINT exec java -jar /usr/opt/my-app/my-app.jar

Create the image

docker build -t my-app:1.0 .

Or a simple container with Java 17 based on alpine linux:

kubectl run java-pod --rm -it --image=openjdk:17-jdk-alpine -- sh

Mock MeterRegistry in JUnit Test

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

private MeterRegistry meterRegistry;

@BeforeEach
void setUp() {
  meterRegistry = new SimpleMeterRegistry();
  Metrics.globalRegistry.add(meterRegistry);
}

@AfterEach
void tearDown() {
  meterRegistry.clear();
  Metrics.globalRegistry.clear();
}

Generate JMeter Test from OpenAPI Spec with openapi-generator-cli and Docker

docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
    -i /local/myspec-1.2.3.yaml \
    -g jmeter \
    -o /local/jmeter